diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 8c37e33..5343e5c 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -12,16 +12,11 @@ jobs: fail-fast: false matrix: - os: [windows-latest, ubuntu-latest, macos-latest] + os: [windows-latest, ubuntu-latest] build_type: [Release] include: - os: windows-latest - build_type: Release - os: ubuntu-latest - build_type: Release - cpp_compiler: g++ - - os: macos-latest - build_type: Release cpp_compiler: g++ steps: @@ -46,14 +41,6 @@ jobs: shell: bash run: | echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" - - - name: Configure CMake (Unix) - if: matrix.os != 'windows-latest' - run: > - cmake -B ${{ steps.strings.outputs.build-output-dir }} - -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} - -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} - -S ${{ github.workspace }} - name: Configure CMake (Windows) if: matrix.os == 'windows-latest' @@ -61,36 +48,70 @@ jobs: cmake -B ${{ steps.strings.outputs.build-output-dir }} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -G "Visual Studio 17 2022" -A x64 + -DFORCE_TESTS=ON -S ${{ github.workspace }} + + - name: Configure CMake (Unix) + if: matrix.os != 'windows-latest' + run: > + cmake -B ${{ steps.strings.outputs.build-output-dir }} + -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -DFORCE_TESTS=ON + -S ${{ github.workspace }} - name: Build run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} + - name: Generate Test Data + run: python ${{ github.workspace }}/tests/generate_test_data.py + working-directory: ${{ github.workspace }} + - name: Test run: ctest --build-config ${{ matrix.build_type }} --output-on-failure --verbose working-directory: ${{ steps.strings.outputs.build-output-dir }} - - name: Upload Linux - if: matrix.os == 'ubuntu-latest' - uses: actions/upload-artifact@v4 - with: - name: palloc-linux - path: build/palloc - if-no-files-found: error - - name: Upload Windows if: matrix.os == 'windows-latest' - uses: actions/upload-artifact@v4 + uses: eXhumer/upload-artifact@0b7d5f5684d3f642f978d2faad9ade64f5b4dd57 with: name: palloc-win - path: build/Release/palloc.exe + path: | + ${{ steps.strings.outputs.build-output-dir }}/palloc-win + !${{ steps.strings.outputs.build-output-dir }}/palloc-win/lib + !${{ steps.strings.outputs.build-output-dir }}/palloc-win/bin/palloc_tests.exe + if-no-files-found: error + + - name: Upload Linux + if: matrix.os == 'ubuntu-latest' + uses: eXhumer/upload-artifact@0b7d5f5684d3f642f978d2faad9ade64f5b4dd57 + with: + name: palloc-linux + path: | + ${{ steps.strings.outputs.build-output-dir }}/palloc-linux + !${{ steps.strings.outputs.build-output-dir }}/palloc-linux/lib/or-tools/examples + !${{ steps.strings.outputs.build-output-dir }}/palloc-linux/lib/or-tools/include + !${{ steps.strings.outputs.build-output-dir }}/palloc-linux/lib/or-tools/Makefile + !${{ steps.strings.outputs.build-output-dir }}/palloc-linux/lib/or-tools/README.md + !${{ steps.strings.outputs.build-output-dir }}/palloc-linux/lib/or-tools/lib/cmake + !${{ steps.strings.outputs.build-output-dir }}/palloc-linux/lib/or-tools/lib/pkgconfig + !${{ steps.strings.outputs.build-output-dir }}/palloc-linux/lib/or-tools/bin + !${{ steps.strings.outputs.build-output-dir }}/palloc-linux/lib/or-tools/share if-no-files-found: error - name: Upload macOS if: matrix.os == 'macos-latest' - uses: actions/upload-artifact@v4 + uses: eXhumer/upload-artifact@0b7d5f5684d3f642f978d2faad9ade64f5b4dd57 with: name: palloc-macos - path: build/palloc + path: | + ${{ steps.strings.outputs.build-output-dir }}/palloc-macos + !${{ steps.strings.outputs.build-output-dir }}/palloc-linux/lib/or-tools/examples + !${{ steps.strings.outputs.build-output-dir }}/palloc-linux/lib/or-tools/include + !${{ steps.strings.outputs.build-output-dir }}/palloc-linux/lib/or-tools/Makefile + !${{ steps.strings.outputs.build-output-dir }}/palloc-linux/lib/or-tools/README.md + !${{ steps.strings.outputs.build-output-dir }}/palloc-linux/lib/or-tools/lib/cmake + !${{ steps.strings.outputs.build-output-dir }}/palloc-linux/lib/or-tools/lib/pkgconfig + !${{ steps.strings.outputs.build-output-dir }}/palloc-linux/lib/or-tools/bin + !${{ steps.strings.outputs.build-output-dir }}/palloc-linux/lib/or-tools/share if-no-files-found: error - diff --git a/.gitignore b/.gitignore index b801dc5..fa94b41 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ FlameGraph summary.txt .progress .progress_lock -job_list.txt \ No newline at end of file +job_list.txt +*.supp \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index ec11f9e..cca092e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,12 +15,25 @@ set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +# Output directory +if(WIN32) + set(PLATFORM_DIR "${CMAKE_BINARY_DIR}/palloc-win") +elseif(LINUX) + set(PLATFORM_DIR "${CMAKE_BINARY_DIR}/palloc-linux") +elseif(APPLE) + set(PLATFORM_DIR "${CMAKE_BINARY_DIR}/palloc-macos") +endif() + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PLATFORM_DIR}/bin") +foreach(OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "${PLATFORM_DIR}/bin") +endforeach() # Project root macro add_definitions(-DPROJECT_ROOT="${CMAKE_SOURCE_DIR}") -if(WIN32) +if(MSVC) set(CMAKE_CXX_FLAGS_DEBUG "/W4 /Od /DEBUG") set(CMAKE_CXX_FLAGS_RELEASE "/O2") set(CMAKE_CXX_FLAGS_PERF "${CMAKE_CXX_FLAGS_RELEASE}") @@ -30,12 +43,14 @@ else() set(CMAKE_CXX_FLAGS_PERF "${CMAKE_CXX_FLAGS_RELEASE} -g -fno-omit-frame-pointer") endif() -message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") -if(CMAKE_BUILD_TYPE) - string(TOUPPER ${CMAKE_BUILD_TYPE} BUILD_TYPE_UPPER) - message(STATUS "${CMAKE_BUILD_TYPE} CXX Flags: ${CMAKE_CXX_FLAGS_${BUILD_TYPE_UPPER}}") +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release") endif() +message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") +string(TOUPPER ${CMAKE_BUILD_TYPE} BUILD_TYPE_UPPER) +message(STATUS "${CMAKE_BUILD_TYPE} CXX Flags: ${CMAKE_CXX_FLAGS_${BUILD_TYPE_UPPER}}") + set(CMAKE_EXPORT_COMPILE_COMMANDS ON) include(FetchContent) @@ -64,7 +79,7 @@ FetchContent_MakeAvailable(glaze) set(ORTOOLS_VERSION "9.12") set(ORTOOLS_BUILD "4544") -if(WIN32) +if(MSVC) set(ORTOOLS_PACKAGE "or-tools_x64_VisualStudio2022_cpp_v${ORTOOLS_VERSION}.${ORTOOLS_BUILD}") set(ORTOOLS_ARCHIVE_EXT "zip") elseif(APPLE) @@ -77,8 +92,8 @@ endif() set(ORTOOLS_URL "https://github.com/google/or-tools/releases/download/v${ORTOOLS_VERSION}/${ORTOOLS_PACKAGE}.${ORTOOLS_ARCHIVE_EXT}") set(ORTOOLS_DOWNLOAD_DIR "${CMAKE_CURRENT_BINARY_DIR}/or-tools-download") -set(ORTOOLS_EXTRACT_DIR "${CMAKE_CURRENT_BINARY_DIR}/or-tools") set(ORTOOLS_ARCHIVE "${ORTOOLS_DOWNLOAD_DIR}/or-tools.${ORTOOLS_ARCHIVE_EXT}") +set(ORTOOLS_EXTRACT_DIR "${PLATFORM_DIR}/lib/or-tools") if(NOT EXISTS "${ORTOOLS_EXTRACT_DIR}") file(DOWNLOAD "${ORTOOLS_URL}" "${ORTOOLS_ARCHIVE}" SHOW_PROGRESS) @@ -95,10 +110,27 @@ if(NOT EXISTS "${ORTOOLS_EXTRACT_DIR}") endif() endif() +if(WIN32) + file(GLOB DLLS "${ORTOOLS_EXTRACT_DIR}/bin/*.dll") + foreach(DLL ${DLLS}) + file(COPY "${DLL}" DESTINATION "${PLATFORM_DIR}/bin") + endforeach() +endif() + list(APPEND CMAKE_PREFIX_PATH "${ORTOOLS_EXTRACT_DIR}") + find_package(ortools REQUIRED) add_subdirectory(src) +# Put test executable in a different directory for Unix +if(UNIX) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/tests") + foreach(OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "${CMAKE_BINARY_DIR}/tests") + endforeach() +endif() + enable_testing() add_subdirectory(tests) diff --git a/include/settings.hpp b/include/settings.hpp index a0de08f..4a01350 100644 --- a/include/settings.hpp +++ b/include/settings.hpp @@ -19,7 +19,7 @@ struct SimulatorSettings { }; struct OutputSettings { - std::filesystem::path path; + std::filesystem::path outputPath; uint32_t numberOfRunsToAggregate; bool prettify; }; diff --git a/scripts/bench.sh b/scripts/bench.sh index 66772d2..e886710 100755 --- a/scripts/bench.sh +++ b/scripts/bench.sh @@ -26,7 +26,7 @@ mkdir -p perf "$PERF_EXE" record --call-graph dwarf,8192 \ -e cycles:u \ --strict-freq \ - ./build/palloc -e data.json + ./build/palloc-linux/bin/palloc -e data.json "$PERF_EXE" script -i perf.data | \ ${FLAMEGRAPH_DIR}/stackcollapse-perf.pl | \ diff --git a/scripts/compile.sh b/scripts/compile.sh index 38e5837..d7e5381 100755 --- a/scripts/compile.sh +++ b/scripts/compile.sh @@ -22,15 +22,15 @@ cd build case "$1" in "release") - cmake .. -DCMAKE_BUILD_TYPE=Release + cmake -DCMAKE_BUILD_TYPE=Release .. echo "Release mode enabled" ;; "perf") - cmake .. -DCMAKE_BUILD_TYPE=Perf + cmake -DCMAKE_BUILD_TYPE=Perf .. echo "Performance profiling mode enabled" ;; *) - cmake .. -DCMAKE_BUILD_TYPE=Debug + cmake -DCMAKE_BUILD_TYPE=Debug .. echo "Debug mode enabled" ;; esac diff --git a/scripts/memcheck.sh b/scripts/memcheck.sh index 4bf3819..7c752b3 100755 --- a/scripts/memcheck.sh +++ b/scripts/memcheck.sh @@ -54,7 +54,7 @@ valgrind --leak-check=full \ --errors-for-leak-kinds=definite \ --error-exitcode=1 \ --suppressions=./ortools.supp \ - ./build/palloc -e "$1" + ./build/palloc-linux/bin/palloc -e "$1" # Remove the temporary suppression file rm ortools.supp \ No newline at end of file diff --git a/scripts/run_experiment.sh b/scripts/run_experiment.sh index 3b4a6c9..9063bec 100755 --- a/scripts/run_experiment.sh +++ b/scripts/run_experiment.sh @@ -8,8 +8,8 @@ if [[ "$(pwd)" != "$PROJECT_ROOT" ]]; then cd "$PROJECT_ROOT" fi -if [ ! -f "build/palloc" ]; then - echo "Error: build/palloc executable not found." +if [ ! -f "./build/palloc-linux/bin/palloc" ]; then + echo "Error: palloc executable not found." echo "Compiling the project..." ./scripts/compile.sh release exit 1 @@ -374,7 +374,7 @@ while read job_info; do output=$(echo $job_info | cut -d'|' -f5) ( - ./build/palloc -e environment.json -o "$output" -d "$duration" -A "$arrival" -r "$rate" -s "$seed" -a "$AGGREGATIONS" -t "$TIMESTEPS" "$WEIGHTS" > /dev/null 2>&1 + ./build/palloc-linux/bin/palloc -e environment.json -o "$output" -d "$duration" -A "$arrival" -r "$rate" -s "$seed" -a "$AGGREGATIONS" -t "$TIMESTEPS" "$WEIGHTS" > /dev/null 2>&1 # Log the run echo "Duration: ${duration}, Arrival: ${arrival}, Rate: ${rate}, Seed: ${seed}" >> "${exp_dir}/summary.txt" diff --git a/scripts/setup.sh b/scripts/setup.sh index 38adfc9..1ed5ebd 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -174,8 +174,8 @@ echo "Generating environment file..." python3 generate_environment.py echo "Generating test data file..." -cd ../tests -python3 generate_test_data.py +cd .. +python3 tests/generate_test_data.py echo "Shutting down OSRM backend..." sudo docker stop $CONTAINER_ID > /dev/null diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 172f5e4..3a9b2ef 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,13 +1,31 @@ file(GLOB_RECURSE LIB_SOURCES "${PROJECT_SOURCE_DIR}/src/*.cpp") list(FILTER LIB_SOURCES EXCLUDE REGEX ".*palloc\\.cpp$") +set(CMAKE_BUILD_RPATH_USE_ORIGIN TRUE) + add_executable(${PROJECT_NAME} "${PROJECT_SOURCE_DIR}/src/palloc.cpp") -if(LIB_SOURCES) - add_library(libpalloc STATIC ${LIB_SOURCES}) - target_include_directories(libpalloc PUBLIC ${CMAKE_SOURCE_DIR}/include) - target_link_libraries(libpalloc PUBLIC ortools::ortools glaze::glaze) - set_target_properties(libpalloc PROPERTIES PREFIX "") - target_link_libraries(${PROJECT_NAME} PRIVATE libpalloc) +add_library(libpalloc STATIC ${LIB_SOURCES}) +target_include_directories(libpalloc PUBLIC ${CMAKE_SOURCE_DIR}/include) +target_link_libraries(libpalloc PUBLIC ortools::ortools glaze::glaze) +set_target_properties(libpalloc PROPERTIES PREFIX "") +target_link_libraries(${PROJECT_NAME} PRIVATE libpalloc) + +# Stop MSVC from making subdirectories +if(MSVC) + set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${PLATFORM_DIR}/bin") +endif() + +if(NOT MSVC) + set_source_files_properties(${PROJECT_NAME}.cpp PROPERTIES COMPILE_FLAGS "-Wno-missing-field-initializers") endif() -set_source_files_properties(${PROJECT_NAME}.cpp PROPERTIES COMPILE_FLAGS "-Wno-missing-field-initializers") +# Move or-tools license file to lib/or-tools +add_custom_command( + TARGET ${PROJECT_NAME} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory "${PLATFORM_DIR}/licenses/or-tools" + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${PLATFORM_DIR}/lib/or-tools/share/doc/ortools/LICENSE" + "${PLATFORM_DIR}/licenses/or-tools/LICENSE" + COMMENT "Copying OR-Tools license to output directory" +) diff --git a/src/palloc.cpp b/src/palloc.cpp index 44f4db3..00e0329 100644 --- a/src/palloc.cpp +++ b/src/palloc.cpp @@ -55,13 +55,13 @@ int main(int argc, char **argv) { "number of aggregates)"}}; argz::parse(about, opts, argc, argv); - if (environmentPathStr.empty() && !about.printed_help) { - std::println(stderr, "Error: Expected environment file"); - return EXIT_FAILURE; + if (about.printed_help || about.printed_version) { + return EXIT_SUCCESS; } if (environmentPathStr.empty()) { - return EXIT_SUCCESS; + std::println(stderr, "Error: Expected environment file"); + return EXIT_FAILURE; } if (simSettings.timesteps < 1) { @@ -95,7 +95,7 @@ int main(int argc, char **argv) { simSettings.seed = seedOpt.value_or(std::chrono::system_clock::now().time_since_epoch().count()); - outputSettings.path = outputPathStr; + outputSettings.outputPath = outputPathStr; GeneralSettings generalSettings{ .numberOfThreads = numberOfThreadsOpt.value_or(std::min( diff --git a/src/simulator.cpp b/src/simulator.cpp index 335da90..2929be7 100644 --- a/src/simulator.cpp +++ b/src/simulator.cpp @@ -108,8 +108,8 @@ void Simulator::simulate(Environment &env, const SimulatorSettings &simSettings, std::println("Average objective cost: {}", globalAvgCost); std::println("Total requests dropped: {}", result.getDroppedRequests()); - if (!outputSettings.path.empty()) { - result.saveToFile(outputSettings.path, outputSettings.prettify); + if (!outputSettings.outputPath.empty()) { + result.saveToFile(outputSettings.outputPath, outputSettings.prettify); } } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 17b17a5..08b26b7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,5 +1,14 @@ # Catch2 -if(CMAKE_BUILD_TYPE STREQUAL "Debug") +option(FORCE_TESTS "Force building tests regardless of build type" OFF) + +if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR FORCE_TESTS) + set(BUILD_TESTS TRUE) +else() + set(BUILD_TESTS FALSE) +endif() + +message(STATUS "Build tests: ${BUILD_TESTS}") +if(BUILD_TESTS) FetchContent_Declare( Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2.git @@ -13,7 +22,7 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug") if (TEST_SOURCES) message(STATUS "Found test sources: ${TEST_SOURCES}") add_executable(palloc_tests ${TEST_SOURCES}) - target_link_libraries(palloc_tests PRIVATE libpalloc Catch2::Catch2WithMain) + target_link_libraries(palloc_tests PUBLIC libpalloc Catch2::Catch2WithMain) include(CTest) include(Catch) diff --git a/tests/generate_test_data.py b/tests/generate_test_data.py index bbe183f..d76821c 100644 --- a/tests/generate_test_data.py +++ b/tests/generate_test_data.py @@ -1,18 +1,27 @@ import json +import os # Data to be written dictionary = { -"dropoff_to_parking": [[1, 2, 3], [4, 5, 6], [7, 8, 9]], -"parking_to_dropoff": [[1, 2, 3], [4, 5, 6], [7, 8, 9]], -"parking_capacities": [3, 5, 9], -"dropoff_coords": [{"latitude": 11, "longitude": 12}, {"latitude": 14, "longitude": 15}, {"latitude": 16, "longitude": 17}], -"parking_coords": [{"latitude": 56, "longitude": 57}, {"latitude": 58, "longitude": 59}, {"latitude": 60, "longitude": 61}], -"smallest_round_trips": [2, 6, 10] + "dropoff_to_parking": [[1, 2, 3], [4, 5, 6], [7, 8, 9]], + "parking_to_dropoff": [[1, 2, 3], [4, 5, 6], [7, 8, 9]], + "parking_capacities": [3, 5, 9], + "dropoff_coords": [{"latitude": 11, "longitude": 12}, {"latitude": 14, "longitude": 15}, {"latitude": 16, "longitude": 17}], + "parking_coords": [{"latitude": 56, "longitude": 57}, {"latitude": 58, "longitude": 59}, {"latitude": 60, "longitude": 61}], + "smallest_round_trips": [2, 6, 10] } # Serializing json json_object = json.dumps(dictionary, indent=4) # Writing to sample.json -with open("test_data.json", "w") as outfile: - outfile.write(json_object) \ No newline at end of file +if not os.path.exists("tests"): + os.makedirs("tests") + +filename = "tests/test_data.json" +with open(filename, "w") as outfile: + outfile.write(json_object) + +script_dir = os.getcwd() +output_path = os.path.join(script_dir, filename) +print(f"Data written to {output_path}") \ No newline at end of file diff --git a/tests/request_generator_test.cpp b/tests/request_generator_test.cpp index 4d5fd3e..2dcfcfa 100644 --- a/tests/request_generator_test.cpp +++ b/tests/request_generator_test.cpp @@ -5,7 +5,7 @@ using namespace palloc; TEST_CASE("Base case - [Request Generator]") { - constexpr int requestRate = 10; + constexpr double requestRate = 10; constexpr int maxTimeTillArrival = 5; constexpr int maxRequestDuration = 10; constexpr int dropoffNodes = 3; @@ -17,8 +17,8 @@ TEST_CASE("Base case - [Request Generator]") { .requestRate = requestRate}); constexpr uint32_t testRequestAmount = 1000; - constexpr auto minRate = std::min(requestRate, 100); - const size_t upperBound = minRate + 3 * std::sqrt(minRate); + const double minRate = std::max(requestRate, 100.0); + const size_t upperBound = std::ceil(minRate + 3 * std::sqrt(minRate)); for (uint32_t i = 0; i < testRequestAmount; ++i) { const auto newRequests = generator.generate(i); diff --git a/tests/simulator_test.cpp b/tests/simulator_test.cpp index 122a204..a1e5168 100644 --- a/tests/simulator_test.cpp +++ b/tests/simulator_test.cpp @@ -11,7 +11,7 @@ TEST_CASE("Base case - [Simulator]") { std::filesystem::path(PROJECT_ROOT) / "tests/temp_result.json"; OutputSettings outputSettings{ - .path = tempResultPath, .numberOfRunsToAggregate = 1, .prettify = true}; + .outputPath = tempResultPath, .numberOfRunsToAggregate = 1, .prettify = true}; GeneralSettings generalSettings{.numberOfThreads = 1}; constexpr auto timesteps = 1000;