Skip to content

Commit

Permalink
Setting up GitHub Actions (#90)
Browse files Browse the repository at this point in the history
* run_all_unit_tests.sh supports selecting a build configuration via argument

* GHA - build Debug and Release unit tests in parallel

* Trying to unflake the unit test of ChildrenTracker

* Working around the flakiness of ExternalToolExecution test

* Setting up ccache

* Setting up ccache

* Setting up ccache

* Setting up ccache

* Setting up ccache

* Setting up ccache

* Setting up ccache

* Setting up ccache

* Setting up ccache

* Setting up ccache

* Setting up ccache

* Typo fix

* Building only the current architecture for unit tests

* First building all unit tests and only then running them

* Marked another flaky test

* Enabled unit tests on each push
  • Loading branch information
mikekazakov authored Jan 12, 2024
1 parent c030d4a commit 29975c4
Show file tree
Hide file tree
Showing 16 changed files with 122 additions and 50 deletions.
41 changes: 36 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,46 @@
name: Build and Test
on: [workflow_dispatch]
on: [push, workflow_dispatch]
jobs:
build:
runs-on: [macos-13]
unit-tests:
runs-on: macos-13
env:
XC_VERSION: ${{ '15.1' }}
CCACHE_COMPRESS: "true"
CCACHE_COMPRESSLEVEL: "10"
CCACHE_MAXSIZE: "400M"
strategy:
matrix:
configuration: ["Debug", "Release"]
steps:
- name: Select latest Xcode
run: "sudo xcode-select -s /Applications/Xcode_$XC_VERSION.app"
- name: Install ccache
run: |
brew install ccache
which ccache
- name: Cache ccache
uses: actions/cache@v2
with:
path: ~/Library/Caches/ccache
key: ccache-${{ github.job }}-${{ matrix.configuration }}-${{ github.run_id }}
restore-keys: ccache-${{ github.job }}-${{ matrix.configuration }}
- uses: actions/checkout@v2
- name: Build and run unit tests
run: "cd Scripts && ./run_all_unit_tests.sh ${{ matrix.configuration }}"
- name: Show ccache stats
run: "ccache --show-stats --verbose"
- name: Remove old ccache caches
run: |
cd Scripts
./run_all_unit_tests.sh
gh extension install actions/gh-actions-cache
echo "Fetching list of cache key"
cacheKeys=$(gh actions-cache list --key ccache-${{ github.job }}-${{ matrix.configuration }}- | cut -f 1 )
## Setting this to not fail the workflow while deleting cache keys.
set +e
echo "Deleting caches..."
for cacheKey in $cacheKeys
do
gh actions-cache delete $cacheKey --confirm
done
echo "Done"
env:
GH_TOKEN: ${{ github.token }}
52 changes: 28 additions & 24 deletions Scripts/run_all_unit_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ BUILD_DIR=$(mktemp -d ${SCRIPTS_DIR}/build.XXXXXXXXX)

ROOT_DIR=$(cd "$SCRIPTS_DIR/.." && pwd)

XCODEPROJ="../Source/NimbleCommander/NimbleCommander.xcodeproj"

LOG_FILE=${BUILD_DIR}/xcodebuild.log

if type -p /usr/local/bin/ccache >/dev/null 2>&1; then
echo Using ccache
export CCACHE_BASEDIR="${ROOT_DIR}"
export CCACHE_SLOPPINESS=time_macros,include_file_mtime,include_file_ctime,file_stat_matches
export CC="${SCRIPTS_DIR}/ccache-clang"
Expand All @@ -34,7 +37,7 @@ build_target()
CONFIGURATION=$2
echo building ${TARGET} - ${CONFIGURATION}
XC="xcodebuild \
-project ../Source/NimbleCommander/NimbleCommander.xcodeproj \
-project ${XCODEPROJ} \
-scheme ${TARGET} \
-configuration ${CONFIGURATION} \
SYMROOT=${BUILD_DIR} \
Expand All @@ -48,35 +51,36 @@ build_target()
}

# list of targets to build
tests=(\
BaseUT \
ConfigUT \
UtilityUT \
VFSIconUT \
VFSUT \
OperationsUT \
ViewerUT \
TermUT \
PanelUT \
NimbleCommanderUT \
)
tests=$(xcodebuild -project ${XCODEPROJ} -list | awk -v word="Schemes:" 'BEGIN {found=0} found {if ($0 ~ /UT$/) print} $0 ~ word {found=1}' | sed 's/^[[:space:]]*//')
echo Building these unit tests: ${tests}

# list of configurations to build the targets with
configurations=(\
Debug \
Release \
)

# run N * M binaries
for configuration in ${configurations[@]}; do
for test in ${tests[@]}; do
if [ -n "$1" ]; then
configurations="$1"
else
configurations="Debug Release"
fi
echo Building these configurations: ${configurations}

# a list of binaries of UTs to execute
binary_paths=()

# build N * M binaries
for configuration in ${configurations}; do
for test in ${tests}; do
# build the binary
build_target $test $configuration
build_target ${test} ${configuration}

# execute the binary
$BINARY_PATH
# store the path to execute later
binary_paths+=("$BINARY_PATH")
done
done

# run the binaries
for path in "${binary_paths[@]}"; do
echo "$path"
$path
done

# cleanup
rm -rf ${BUILD_DIR}
1 change: 1 addition & 0 deletions Source/Base/config/tests.xcconfig
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
OTHER_LDFLAGS = $(inherited) -lBase
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) GTEST_DONT_DEFINE_FAIL=1 GTEST_DONT_DEFINE_SUCCEED=1
EXECUTABLE_PREFIX=
ONLY_ACTIVE_ARCH=YES
1 change: 1 addition & 0 deletions Source/Config/config/tests.xcconfig
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
OTHER_LDFLAGS = $(inherited) -lConfig -lBase -framework Foundation -framework Cocoa
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) GTEST_DONT_DEFINE_FAIL=1 GTEST_DONT_DEFINE_SUCCEED=1
EXECUTABLE_PREFIX=
ONLY_ACTIVE_ARCH=YES
Original file line number Diff line number Diff line change
Expand Up @@ -1770,6 +1770,7 @@
CFA1716220196CB400F507D5 /* AppDelegate+MainWindowCreation.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "AppDelegate+MainWindowCreation.mm"; path = "NimbleCommander/Bootstrap/AppDelegate+MainWindowCreation.mm"; sourceTree = SOURCE_ROOT; };
CFA1716420196CF600F507D5 /* AppDelegate.Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.Private.h; path = NimbleCommander/Bootstrap/AppDelegate.Private.h; sourceTree = SOURCE_ROOT; };
CFA1716820199E8700F507D5 /* FavoritesImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FavoritesImpl.h; path = NimbleCommander/States/FilePanels/FavoritesImpl.h; sourceTree = SOURCE_ROOT; };
CFA22D2B2B51BD7E00E71479 /* tests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = tests.xcconfig; path = NimbleCommander/Config/tests.xcconfig; sourceTree = "<group>"; };
CFA2D6011E39D43C009DB708 /* PreferencesWindowThemesTabImportSheet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PreferencesWindowThemesTabImportSheet.h; path = NimbleCommander/Preferences/PreferencesWindowThemesTabImportSheet.h; sourceTree = SOURCE_ROOT; };
CFA2D6021E39D43C009DB708 /* PreferencesWindowThemesTabImportSheet.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = PreferencesWindowThemesTabImportSheet.mm; path = NimbleCommander/Preferences/PreferencesWindowThemesTabImportSheet.mm; sourceTree = SOURCE_ROOT; };
CFA3D7C519625F71006A30A1 /* GTMHotKeyTextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTMHotKeyTextField.h; path = ../../3rd_Party/GTMHotKeyTextField/GTMHotKeyTextField.h; sourceTree = SOURCE_ROOT; };
Expand Down Expand Up @@ -2221,6 +2222,7 @@
CF6572991FA6CD7300ABF85F /* debug.xcconfig */,
CF5FD92D1FA2C02E00752E59 /* default.xcconfig */,
CF65729A1FA6CD8100ABF85F /* release.xcconfig */,
CFA22D2B2B51BD7E00E71479 /* tests.xcconfig */,
);
name = Config;
sourceTree = "<group>";
Expand Down Expand Up @@ -5142,13 +5144,15 @@
};
CF5DE0B7258414CA00604DEE /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = CFA22D2B2B51BD7E00E71479 /* tests.xcconfig */;
buildSettings = {
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
CF5DE0B8258414CA00604DEE /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = CFA22D2B2B51BD7E00E71479 /* tests.xcconfig */;
buildSettings = {
PRODUCT_NAME = "$(TARGET_NAME)";
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ONLY_ACTIVE_ARCH=YES
1 change: 1 addition & 0 deletions Source/Operations/config/tests.xcconfig
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
OTHER_LDFLAGS = $(inherited) -lgmock -lgtest -lOperations -lRoutedIO -lUtility
LD_RUNPATH_SEARCH_PATHS = @executable_path/
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) GTEST_DONT_DEFINE_FAIL=1 GTEST_DONT_DEFINE_SUCCEED=1
ONLY_ACTIVE_ARCH=YES
1 change: 1 addition & 0 deletions Source/Panel/config/tests.xcconfig
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
LD_RUNPATH_SEARCH_PATHS = @executable_path/
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) GTEST_DONT_DEFINE_FAIL=1 GTEST_DONT_DEFINE_SUCCEED=1
OTHER_LDFLAGS = $(inherited) -framework Foundation -framework Cocoa -lUtility -lBase -lPanel -lVFS -lRoutedIO
ONLY_ACTIVE_ARCH=YES
6 changes: 3 additions & 3 deletions Source/Panel/tests/ExternalTools_IT.mm
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2023 Michael Kazakov. Subject to GNU General Public License version 3.
// Copyright (C) 2023-2024 Michael Kazakov. Subject to GNU General Public License version 3.
// TODO: add a PanelIT target

#include "ExternalTools.h"
Expand Down Expand Up @@ -142,7 +142,7 @@ static bool WaitForChildProcess(int _pid, std::chrono::nanoseconds _deadline, st
}
}

TEST_CASE(PREFIX "execute a ui app")
TEST_CASE(PREFIX "execute a ui app", "[!mayfail]")
{
TempTestDir dir;
const auto basedir = dir.directory;
Expand Down Expand Up @@ -235,7 +235,7 @@ static bool WaitForChildProcess(int _pid, std::chrono::nanoseconds _deadline, st
auto run = [&] {
ExternalToolExecution ex{ctx, et};
auto pid = ex.StartDetachedUI();
REQUIRE(pid);
REQUIRE(pid); // <-- Flaky!
REQUIRE(WaitForChildProcess(*pid, 5s, 1ms));
};

Expand Down
1 change: 1 addition & 0 deletions Source/Term/config/tests.xcconfig
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) GTEST_DONT_DEFINE_FAIL=1 GTEST_DONT_DEFINE_SUCCEED=1
OTHER_LDFLAGS = $(inherited) -framework Foundation -framework Cocoa -lTerm -lUtility -lBase
ONLY_ACTIVE_ARCH=YES
53 changes: 38 additions & 15 deletions Source/Term/tests/ChildrenTracker_UT.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2023 Michael Kazakov. Subject to GNU General Public License version 3.
// Copyright (C) 2023-2024 Michael Kazakov. Subject to GNU General Public License version 3.

#include "Tests.h"
#include "AtomicHolder.h"
Expand All @@ -10,44 +10,51 @@ using namespace nc::term;
using namespace std::chrono_literals;
#define PREFIX "nc::term::ChildrenTracker "

TEST_CASE(PREFIX "Generic cases")
TEST_CASE(PREFIX "Generic cases", "[!mayfail]")
{
const int mypid = getpid();
const int p1 = getpid();
QueuedAtomicHolder<int> ncalled{0};
auto cb = [&ncalled, next = 1] mutable { ncalled.store(next++); };

ChildrenTracker tracker{mypid, cb};
ChildrenTracker tracker{p1, cb};

SECTION("Nothing") {}
SECTION("Single fork")
{
int p2;
int p2 = 0;
if( (p2 = fork()) == 0 ) {
std::this_thread::sleep_for(1ms);
exit(0);
}
CHECK(p2 > 0);
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 1)); // p1 fork -> p2
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 2)); // p2 exit
waitpid(p2, nullptr, 0);
CHECK(waitpid(p2, nullptr, 0) == p2);
}
SECTION("Two sequent forks")
{
if( fork() == 0 ) {
int p2 = 0, p3 = 0;
if( (p2 = fork()) == 0 ) {
std::this_thread::sleep_for(1ms);
exit(0);
}
CHECK(p2 > 0);
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 1)); // p1 fork -> p2
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 2)); // p2 exit
if( fork() == 0 ) {
if( (p3 = fork()) == 0 ) {
std::this_thread::sleep_for(1ms);
exit(0);
}
CHECK(p3 > 0);
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 3)); // p1 fork -> p3
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 4)); // p3 exit
CHECK(waitpid(p2, nullptr, 0) == p2);
CHECK(waitpid(p3, nullptr, 0) == p3);
}
SECTION("Two recursive forks")
{
if( fork() == 0 ) {
int p2 = 0;
if( (p2 = fork()) == 0 ) {
std::this_thread::sleep_for(1ms);
if( int p3; (p3 = fork()) == 0 ) {
std::this_thread::sleep_for(1ms);
Expand All @@ -57,14 +64,17 @@ TEST_CASE(PREFIX "Generic cases")
waitpid(p3, nullptr, 0);
exit(0);
}
CHECK(p2 > 0);
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 1)); // p1 fork -> p2
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 2)); // p2 fork -> p3
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 3)); // p3 exit
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 4)); // p2 exit
CHECK(waitpid(p2, nullptr, 0) == p2);
}
SECTION("Three recursive forks")
{
if( fork() == 0 ) {
int p2 = 0;
if( (p2 = fork()) == 0 ) {
std::this_thread::sleep_for(1ms);
if( int p3; (p3 = fork()) == 0 ) {
std::this_thread::sleep_for(1ms);
Expand All @@ -80,16 +90,19 @@ TEST_CASE(PREFIX "Generic cases")
waitpid(p3, nullptr, 0);
exit(0);
}
CHECK(p2 > 0);
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 1)); // p1 fork -> p2
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 2)); // p2 fork -> p3
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 3)); // p3 fork -> p4
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 4)); // p4 exit
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 5)); // p3 exit
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 6)); // p2 exit
CHECK(waitpid(p2, nullptr, 0) == p2);
}
SECTION("2 x two recursive forks")
{
if( fork() == 0 ) {
int p2 = 0, p4 = 0;
if( (p2 = fork()) == 0 ) {
std::this_thread::sleep_for(1ms);
if( int p3; (p3 = fork()) == 0 ) {
std::this_thread::sleep_for(1ms);
Expand All @@ -99,7 +112,7 @@ TEST_CASE(PREFIX "Generic cases")
waitpid(p3, nullptr, 0);
exit(0);
}
if( fork() == 0 ) {
if( (p4 = fork()) == 0 ) {
std::this_thread::sleep_for(1ms);
if( int p3; (p3 = fork()) == 0 ) {
std::this_thread::sleep_for(1ms);
Expand All @@ -109,29 +122,37 @@ TEST_CASE(PREFIX "Generic cases")
waitpid(p3, nullptr, 0);
exit(0);
}
CHECK(p2 > 0);
CHECK(p4 > 0);
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 1)); // p1 fork -> p2
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 2)); // p1 fork -> p4
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 3)); // p2 fork -> p3
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 4)); // p4 fork -> p5
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 5)); // p3 exit
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 6)); // p5 exit
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 7)); // p2 exit
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 8)); // p4 exit
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 8)); // p4 exit <-- this one fails on GHA
CHECK(waitpid(p2, nullptr, 0) == p2);
CHECK(waitpid(p4, nullptr, 0) == p4);
}
SECTION("Fork and exec")
{
if( fork() == 0 ) {
int p2 = 0;
if( (p2 = fork()) == 0 ) {
std::this_thread::sleep_for(1ms);
close(1);
execl("/usr/bin/uptime", "uptime", nullptr);
}
CHECK(p2 > 0);
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 1)); // p1 fork -> p2
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 2)); // p2 exec
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 3)); // p2 exit
CHECK(waitpid(p2, nullptr, 0) == p2);
}
SECTION("Two recursive forks and exec")
{
if( fork() == 0 ) {
int p2 = 0;
if( (p2 = fork()) == 0 ) {
std::this_thread::sleep_for(1ms);
if( int p3; (p3 = fork()) == 0 ) {
std::this_thread::sleep_for(1ms);
Expand All @@ -142,11 +163,13 @@ TEST_CASE(PREFIX "Generic cases")
waitpid(p3, nullptr, 0);
exit(0);
}
CHECK(p2 > 0);
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 1)); // p1 fork -> p2
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 2)); // p2 fork -> p3
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 3)); // p3 exec
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 4)); // p3 exit
CHECK(ncalled.wait_to_become_with_runloop(5s, 1ms, 5)); // p2 exit
CHECK(waitpid(p2, nullptr, 0) == p2);
}
}

Expand Down
1 change: 1 addition & 0 deletions Source/Utility/config/tests.xcconfig
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
OTHER_LDFLAGS = $(inherited) -lUtility -lBase
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) GTEST_DONT_DEFINE_FAIL=1 GTEST_DONT_DEFINE_SUCCEED=1
EXECUTABLE_PREFIX=
ONLY_ACTIVE_ARCH=YES
Loading

0 comments on commit 29975c4

Please sign in to comment.