From 2f18585d3a52cd7a43c3f9c42e28bb6133066609 Mon Sep 17 00:00:00 2001 From: Matthew Asplund Date: Sat, 19 Feb 2022 09:14:46 -0800 Subject: [PATCH] Add Incremental builds for exes with no output (#117) * Add support for executable and evaluate timestamp incremental builds * Bump version --- Source/Client/CLI/Recipe.toml | 2 +- .../CLI/Source/Commands/VersionCommand.h | 2 +- .../Core/Source/Build/BuildEvaluateEngine.h | 22 ++- .../Core/Source/Build/BuildHistoryChecker.h | 42 +++++- .../Core/Source/Build/FileSystemState.h | 13 +- .../Core/Source/Build/RecipeBuildRunner.h | 1 + Source/Client/Core/Source/Module.cpp | 5 +- .../OperationGraph/OperationGraphReader.h | 23 ++- .../OperationGraph/OperationGraphWriter.h | 15 +- .../Source/OperationGraph/OperationInfo.h | 7 + .../Build/BuildEvaluateEngineTests.h | 142 +++++++++++++++++- .../Build/BuildHistoryCheckerTests.h | 32 ++-- .../UnitTests/Build/FileSystemStateTests.h | 13 +- .../OperationGraphManagerTests.h | 16 +- .../OperationGraphReaderTests.h | 120 ++++++++------- .../OperationGraph/OperationGraphTests.h | 9 ++ .../OperationGraphWriterTests.h | 34 +++-- .../Client/Core/UnitTests/Utils/TestHelpers.h | 19 +-- .../gen/Build/BuildEvaluateEngineTests.gen.h | 1 + Source/Client/Core/UnitTests/gen/Main.cpp | 1 + .../CompilerArgumentBuilderUnitTests.cs | 1 + .../Runtime/OperationGraph/OperationInfo.cs | 7 + .../OperationGraph/OperationGraphReader.cs | 34 +++-- .../OperationGraph/OperationGraphWriter.cs | 7 +- Source/Installer/SoupInstaller/Setup.cs | 2 +- Source/Tools/Mkdir/Main.cpp | 39 +++++ 26 files changed, 451 insertions(+), 158 deletions(-) diff --git a/Source/Client/CLI/Recipe.toml b/Source/Client/CLI/Recipe.toml index 8f7ce273b..6177fa877 100644 --- a/Source/Client/CLI/Recipe.toml +++ b/Source/Client/CLI/Recipe.toml @@ -1,6 +1,6 @@ Name = "Soup" Language = "C++" -Version = "0.17.2" +Version = "0.17.3" Type = "Executable" Source = [ diff --git a/Source/Client/CLI/Source/Commands/VersionCommand.h b/Source/Client/CLI/Source/Commands/VersionCommand.h index 25ee008e0..3aec57417 100644 --- a/Source/Client/CLI/Source/Commands/VersionCommand.h +++ b/Source/Client/CLI/Source/Commands/VersionCommand.h @@ -31,7 +31,7 @@ namespace Soup::Client // TODO var version = Assembly.GetExecutingAssembly().GetName().Version; // Log::Message($"{version.Major}.{version.Minor}.{version.Build}"); - Log::HighPriority("0.17.2"); + Log::HighPriority("0.17.3"); } private: diff --git a/Source/Client/Core/Source/Build/BuildEvaluateEngine.h b/Source/Client/Core/Source/Build/BuildEvaluateEngine.h index eba499009..4e8364191 100644 --- a/Source/Client/Core/Source/Build/BuildEvaluateEngine.h +++ b/Source/Client/Core/Source/Build/BuildEvaluateEngine.h @@ -106,10 +106,21 @@ namespace Soup::Core auto buildRequired = false; if (operationInfo.WasSuccessfulRun) { + // Check if the executable has changed since the last run + bool executableOutOfDate = false; + if (operationInfo.Command.Executable != Path("writefile.exe")) + { + // Only check for "real" executables + auto executableFileId = _fileSystemState->ToFileId(operationInfo.Command.Executable, operationInfo.Command.WorkingDirectory); + if (_stateChecker.IsOutdated(operationInfo.EvaluateTime, executableFileId)) + { + executableOutOfDate = true; + } + } + // Perform the incremental build checks - if (_stateChecker.IsOutdated( - operationInfo.ObservedOutput, - operationInfo.ObservedInput)) + if (executableOutOfDate || + _stateChecker.IsOutdated(operationInfo.ObservedOutput, operationInfo.ObservedInput)) { buildRequired = true; } @@ -185,6 +196,7 @@ namespace Soup::Core // Mark this operation as successful to enable future incremental builds operationInfo.WasSuccessfulRun = true; + operationInfo.EvaluateTime = std::chrono::system_clock::now(); // Ensure the File System State is notified of any output files that have changed _fileSystemState->CheckFileWriteTimes(operationInfo.ObservedOutput); @@ -282,11 +294,9 @@ namespace Soup::Core operationInfo.ObservedInput = _fileSystemState->ToFileIds(input, operationInfo.Command.WorkingDirectory); operationInfo.ObservedOutput = _fileSystemState->ToFileIds(output, operationInfo.Command.WorkingDirectory); - // Add the executable as actual input too - // TODO: operationInfo.ObservedInput.push_back(_fileSystemState->ToFileId(operationInfo.Command.Executable, operationInfo.Command.WorkingDirectory)); - // Mark this operation as successful to enable future incremental builds operationInfo.WasSuccessfulRun = true; + operationInfo.EvaluateTime = std::chrono::system_clock::now(); // Ensure the File System State is notified of any output files that have changed _fileSystemState->CheckFileWriteTimes(operationInfo.ObservedOutput); diff --git a/Source/Client/Core/Source/Build/BuildHistoryChecker.h b/Source/Client/Core/Source/Build/BuildHistoryChecker.h index 16ef97f67..eba5ea67c 100644 --- a/Source/Client/Core/Source/Build/BuildHistoryChecker.h +++ b/Source/Client/Core/Source/Build/BuildHistoryChecker.h @@ -15,6 +15,39 @@ namespace Soup::Core { } + /// + /// Perform a check if the last evaluate time is outdated with + /// respect to the input files + /// + bool IsOutdated( + std::chrono::time_point lastEvaluateTime, + FileId inputFile) + { + auto lastWriteTime = _fileSystemState->GetLastWriteTime(inputFile); + + // Perform the final check + if (!lastWriteTime.has_value()) + { + // The input was missing + auto targetFilePath = _fileSystemState->GetFilePath(inputFile); + Log::Info("Input Missing [" + targetFilePath.ToString() + "]"); + return true; + } + else + { + if (lastWriteTime.value() > lastEvaluateTime) + { + auto targetFilePath = _fileSystemState->GetFilePath(inputFile); + Log::Info("Input altered after last evaluate [" + targetFilePath.ToString() + "]"); + return true; + } + else + { + return false; + } + } + } + /// /// Perform a check if the requested target is outdated with /// respect to the input files @@ -48,7 +81,7 @@ namespace Soup::Core const std::vector& inputFiles) { // Get the output file last write time - std::optional targetFileLastWriteTime = _fileSystemState->GetLastWriteTime(targetFile); + auto targetFileLastWriteTime = _fileSystemState->GetLastWriteTime(targetFile); if (!targetFileLastWriteTime.has_value()) { @@ -70,10 +103,13 @@ namespace Soup::Core return false; } - bool IsOutdated(FileId inputFile, FileId outputFile, std::time_t outputFileLastWriteTime) + bool IsOutdated( + FileId inputFile, + FileId outputFile, + std::chrono::time_point outputFileLastWriteTime) { // Get the file state from the cache - std::optional lastWriteTime = _fileSystemState->GetLastWriteTime(inputFile); + auto lastWriteTime = _fileSystemState->GetLastWriteTime(inputFile); // Perform the final check if (!lastWriteTime.has_value()) diff --git a/Source/Client/Core/Source/Build/FileSystemState.h b/Source/Client/Core/Source/Build/FileSystemState.h index 9999f2693..4be8aef7d 100644 --- a/Source/Client/Core/Source/Build/FileSystemState.h +++ b/Source/Client/Core/Source/Build/FileSystemState.h @@ -41,7 +41,7 @@ namespace Soup::Core FileSystemState( FileId maxFileId, std::unordered_map files, - std::unordered_map> writeCache) : + std::unordered_map>> writeCache) : _maxFileId(maxFileId), _files(std::move(files)), _fileLookup(), @@ -86,7 +86,7 @@ namespace Soup::Core /// /// Find the write time for a given file id /// - std::optional GetLastWriteTime(FileId file) + std::optional> GetLastWriteTime(FileId file) { auto findResult = _writeCache.find(file); if (findResult != _writeCache.end()) @@ -196,16 +196,17 @@ namespace Soup::Core /// /// Update the write times for the provided set of files /// - std::optional CheckFileWriteTime(FileId fileId) + std::optional> CheckFileWriteTime(FileId fileId) { auto& filePath = GetFilePath(fileId); // The file does not exist in the cache // Load the actual value and save it for later - std::optional lastWriteTime = std::nullopt; + std::optional> lastWriteTime = std::nullopt; if (System::IFileSystem::Current().Exists(filePath)) { - lastWriteTime = System::IFileSystem::Current().GetLastWriteTime(filePath); + lastWriteTime = std::chrono::system_clock::from_time_t( + System::IFileSystem::Current().GetLastWriteTime(filePath)); } auto insertResult = _writeCache.insert_or_assign(fileId, lastWriteTime); @@ -220,6 +221,6 @@ namespace Soup::Core std::unordered_map _files; std::unordered_map _fileLookup; - std::unordered_map> _writeCache; + std::unordered_map>> _writeCache; }; } diff --git a/Source/Client/Core/Source/Build/RecipeBuildRunner.h b/Source/Client/Core/Source/Build/RecipeBuildRunner.h index 3bb9d7b82..5a275a4e4 100644 --- a/Source/Client/Core/Source/Build/RecipeBuildRunner.h +++ b/Source/Client/Core/Source/Build/RecipeBuildRunner.h @@ -727,6 +727,7 @@ namespace Soup::Core if (previousOperationGraph.TryFindOperationInfo(activeOperationInfo.Command, previousOperationInfo)) { activeOperationInfo.WasSuccessfulRun = previousOperationInfo->WasSuccessfulRun; + activeOperationInfo.EvaluateTime = previousOperationInfo->EvaluateTime; activeOperationInfo.ObservedInput = previousOperationInfo->ObservedInput; activeOperationInfo.ObservedOutput = previousOperationInfo->ObservedOutput; } diff --git a/Source/Client/Core/Source/Module.cpp b/Source/Client/Core/Source/Module.cpp index ef7b70375..e9489dd20 100644 --- a/Source/Client/Core/Source/Module.cpp +++ b/Source/Client/Core/Source/Module.cpp @@ -24,12 +24,9 @@ #include -#ifdef max #undef max -#endif -#ifdef CreateProcess +#undef min #undef CreateProcess -#endif #include diff --git a/Source/Client/Core/Source/OperationGraph/OperationGraphReader.h b/Source/Client/Core/Source/OperationGraph/OperationGraphReader.h index eee0d7369..a2c675a1e 100644 --- a/Source/Client/Core/Source/OperationGraph/OperationGraphReader.h +++ b/Source/Client/Core/Source/OperationGraph/OperationGraphReader.h @@ -14,7 +14,10 @@ namespace Soup::Core { private: // Binary Operation Graph file format - static constexpr uint32_t FileVersion = 3; + static constexpr uint32_t FileVersion = 4; + + // The offset from January 1, 1970 at 00:00:00.000 to January 1, 0001 at 00:00:00.000 in the Gregorian calendar + static constexpr long long UnixEpochOffset = 62135596800000; public: static OperationGraph Deserialize(std::istream& stream) @@ -139,6 +142,11 @@ namespace Soup::Core // Write out the value indicating if there was a successful run auto wasSuccessfulRun = ReadBoolean(stream); + // Read the utc tick since January 1, 0001 at 00:00:00.000 in the Gregorian calendar + auto evaluateTimeMilliseconds = ReadInt64(stream); + auto unixEvaluateTimeMilliseconds = std::chrono::milliseconds(evaluateTimeMilliseconds - UnixEpochOffset); + auto evaluateTime = std::chrono::time_point(unixEvaluateTimeMilliseconds); + // Write out the observed input files auto observedInput = ReadFileIdList(stream); @@ -159,6 +167,7 @@ namespace Soup::Core std::move(children), dependecyCount, wasSuccessfulRun, + evaluateTime, std::move(observedInput), std::move(observedOutput)); } @@ -175,6 +184,18 @@ namespace Soup::Core return result; } + static int64_t ReadInt64(std::istream& stream) + { + int64_t result = 0; + stream.read(reinterpret_cast(&result), sizeof(int64_t)); + if (stream.fail()) + { + throw std::runtime_error("OperationGraphReader Failed to read 64 bit integer value"); + } + + return result; + } + static boolean ReadBoolean(std::istream& stream) { uint32_t result = 0; diff --git a/Source/Client/Core/Source/OperationGraph/OperationGraphWriter.h b/Source/Client/Core/Source/OperationGraph/OperationGraphWriter.h index 2319138f9..43fc16f14 100644 --- a/Source/Client/Core/Source/OperationGraph/OperationGraphWriter.h +++ b/Source/Client/Core/Source/OperationGraph/OperationGraphWriter.h @@ -14,7 +14,10 @@ namespace Soup::Core { private: // Binary Operation graph file format - static constexpr uint32_t FileVersion = 3; + static constexpr uint32_t FileVersion = 4; + + // The offset from January 1, 1970 at 00:00:00.000 to January 1, 0001 at 00:00:00.000 in the Gregorian calendar + static constexpr long long UnixEpochOffset = 62135596800000; public: static void Serialize(const OperationGraph& state, std::ostream& stream) @@ -87,6 +90,11 @@ namespace Soup::Core // Write out the value indicating if there was a successful run WriteValue(stream, operation.WasSuccessfulRun); + // Write out the utc milliseconds since January 1, 0001 at 00:00:00.000 in the Gregorian calendar + auto unixEvaluateTimeMilliseconds = std::chrono::time_point_cast(operation.EvaluateTime).time_since_epoch().count(); + auto evaluateTimeMilliseconds = unixEvaluateTimeMilliseconds + UnixEpochOffset; + WriteValue(stream, evaluateTimeMilliseconds); + // Write out the observed input files WriteValues(stream, operation.ObservedInput); @@ -99,6 +107,11 @@ namespace Soup::Core stream.write(reinterpret_cast(&value), sizeof(uint32_t)); } + static void WriteValue(std::ostream& stream, int64_t value) + { + stream.write(reinterpret_cast(&value), sizeof(int64_t)); + } + static void WriteValue(std::ostream& stream, bool value) { uint32_t integerValue = value ? 1u : 0u; diff --git a/Source/Client/Core/Source/OperationGraph/OperationInfo.h b/Source/Client/Core/Source/OperationGraph/OperationInfo.h index f3baa6948..c11568029 100644 --- a/Source/Client/Core/Source/OperationGraph/OperationInfo.h +++ b/Source/Client/Core/Source/OperationGraph/OperationInfo.h @@ -4,6 +4,7 @@ #pragma once #include "FileSystemState.h" +using namespace std::chrono_literals; namespace Soup::Core { @@ -67,6 +68,7 @@ namespace Soup::Core Children(), DependencyCount(0), WasSuccessfulRun(false), + EvaluateTime(std::chrono::time_point::min()), ObservedInput(), ObservedOutput() { @@ -93,6 +95,7 @@ namespace Soup::Core Children(), DependencyCount(0), WasSuccessfulRun(false), + EvaluateTime(std::chrono::time_point::min()), ObservedInput(), ObservedOutput() { @@ -112,6 +115,7 @@ namespace Soup::Core std::vector children, uint32_t dependencyCount, bool wasSuccessfulRun, + std::chrono::time_point evaluateTime, std::vector observedInput, std::vector observedOutput) : Id(id), @@ -124,6 +128,7 @@ namespace Soup::Core Children(std::move(children)), DependencyCount(dependencyCount), WasSuccessfulRun(wasSuccessfulRun), + EvaluateTime(evaluateTime), ObservedInput(std::move(observedInput)), ObservedOutput(std::move(observedOutput)) { @@ -144,6 +149,7 @@ namespace Soup::Core Children == rhs.Children && DependencyCount == rhs.DependencyCount && WasSuccessfulRun == rhs.WasSuccessfulRun && + EvaluateTime == rhs.EvaluateTime && ObservedInput == rhs.ObservedInput && ObservedOutput == rhs.ObservedOutput; } @@ -158,6 +164,7 @@ namespace Soup::Core std::vector Children; uint32_t DependencyCount; bool WasSuccessfulRun; + std::chrono::time_point EvaluateTime; std::vector ObservedInput; std::vector ObservedOutput; }; diff --git a/Source/Client/Core/UnitTests/Build/BuildEvaluateEngineTests.h b/Source/Client/Core/UnitTests/Build/BuildEvaluateEngineTests.h index a74b5cc3d..007d41c81 100644 --- a/Source/Client/Core/UnitTests/Build/BuildEvaluateEngineTests.h +++ b/Source/Client/Core/UnitTests/Build/BuildEvaluateEngineTests.h @@ -3,6 +3,7 @@ // #pragma once +using namespace std::chrono; namespace Soup::Core::UnitTests { @@ -117,6 +118,7 @@ namespace Soup::Core::UnitTests { }, 1, false, + std::chrono::time_point::min(), { }, { }), }); @@ -175,6 +177,8 @@ namespace Soup::Core::UnitTests auto testListener = std::make_shared(); auto scopedTraceListener = ScopedTraceListenerRegister(testListener); + auto executableInputTime = std::chrono::sys_days(May/22/2015) + 9h + 9min; + // Register the test file system auto fileSystem = std::make_shared(); auto scopedFileSystem = ScopedFileSystemRegister(fileSystem); @@ -183,6 +187,10 @@ namespace Soup::Core::UnitTests std::unordered_map({ { 1, Path("C:/TestWorkingDirectory/InputFile.in") }, { 2, Path("C:/TestWorkingDirectory/OutputFile.out") }, + { 3, Path("C:/TestWorkingDirectory/Command.exe") }, + }), + std::unordered_map>>({ + { 3, executableInputTime }, })); // Register the test process manager @@ -210,6 +218,7 @@ namespace Soup::Core::UnitTests { }, 1, true, + std::chrono::sys_days(May/22/2015) + 9h + 10min, { 1, }, { 2, }), }); @@ -271,7 +280,8 @@ namespace Soup::Core::UnitTests auto scopedTraceListener = ScopedTraceListenerRegister(testListener); // Setup the input file only - auto inputTime = CreateDateTime(2015, 5, 22, 9, 11); + auto inputTime = std::chrono::sys_days(May/22/2015) + 9h + 11min; + auto executableInputTime = std::chrono::sys_days(May/22/2015) + 9h + 9min; // Register the test file system auto fileSystem = std::make_shared(); @@ -281,10 +291,12 @@ namespace Soup::Core::UnitTests std::unordered_map({ { 1, Path("C:/TestWorkingDirectory/InputFile.in") }, { 2, Path("C:/TestWorkingDirectory/OutputFile.out") }, + { 3, Path("C:/TestWorkingDirectory/Command.exe") }, }), - std::unordered_map>({ + std::unordered_map>>({ { 1, inputTime }, { 2, std::nullopt }, + { 3, executableInputTime }, })); // Register the test process manager @@ -312,6 +324,7 @@ namespace Soup::Core::UnitTests { }, 1, true, + std::chrono::sys_days(May/22/2015) + 9h + 10min, { 1, }, { 2, }), }); @@ -371,8 +384,9 @@ namespace Soup::Core::UnitTests auto scopedTraceListener = ScopedTraceListenerRegister(testListener); // Setup the input/output files to be out of date - auto outputTime = CreateDateTime(2015, 5, 22, 9, 10); - auto inputTime = CreateDateTime(2015, 5, 22, 9, 11); + auto outputTime = std::chrono::sys_days(May/22/2015) + 9h + 10min; + auto inputTime = std::chrono::sys_days(May/22/2015) + 9h + 11min; + auto executableInputTime = std::chrono::sys_days(May/22/2015) + 9h + 9min; // Register the test file system auto fileSystem = std::make_shared(); @@ -382,10 +396,12 @@ namespace Soup::Core::UnitTests std::unordered_map({ { 1, Path("C:/TestWorkingDirectory/InputFile.in") }, { 2, Path("C:/TestWorkingDirectory/OutputFile.out") }, + { 3, Path("C:/TestWorkingDirectory/Command.exe") }, }), - std::unordered_map>({ + std::unordered_map>>({ { 1, inputTime }, { 2, outputTime }, + { 3, executableInputTime }, })); // Register the test process manager @@ -413,6 +429,7 @@ namespace Soup::Core::UnitTests { }, 1, true, + std::chrono::sys_days(May/22/2015) + 9h + 10min, { 1, }, { 2, }), }); @@ -464,6 +481,111 @@ namespace Soup::Core::UnitTests "Verify detour process manager requests match expected."); } + // [[Fact]] + void Evaluate_OneOperation_Incremental_Executable_OutOfDate() + { + // Register the test listener + auto testListener = std::make_shared(); + auto scopedTraceListener = ScopedTraceListenerRegister(testListener); + + // Setup the input/output files to be out of date + auto outputTime = std::chrono::sys_days(May/22/2015) + 9h + 10min; + auto inputTime = std::chrono::sys_days(May/22/2015) + 9h + 9min; + auto executableInputTime = std::chrono::sys_days(May/22/2015) + 9h + 11min; + + // Register the test file system + auto fileSystem = std::make_shared(); + auto scopedFileSystem = ScopedFileSystemRegister(fileSystem); + auto fileSystemState = std::make_shared( + 3, + std::unordered_map({ + { 1, Path("C:/TestWorkingDirectory/InputFile.in") }, + { 2, Path("C:/TestWorkingDirectory/OutputFile.out") }, + { 3, Path("C:/TestWorkingDirectory/Command.exe") }, + }), + std::unordered_map>>({ + { 1, inputTime }, + { 2, outputTime }, + { 3, executableInputTime }, + })); + + // Register the test process manager + auto detourProcessManager = std::make_shared(); + auto scopedDetourProcesManager = Monitor::ScopedDetourProcessManagerRegister(detourProcessManager); + + // Setup the input build state + auto operationGraph = OperationGraph( + { + { 1, Path("C:/Folder/File.txt") }, + }, + { 1, }, + { + OperationInfo( + 1, + "TestCommand: 1", + CommandInfo( + Path("C:/TestWorkingDirectory/"), + Path("./Command.exe"), + "Arguments"), + { 1, }, + { 2, }, + { }, + { }, + { }, + 1, + true, + std::chrono::sys_days(May/22/2015) + 9h + 0min, + { 1, }, + { 2, }), + }); + auto temporaryDirectory = Path(); + auto globalAllowedReadAccess = std::vector(); + auto globalAllowedWriteAccess = std::vector(); + auto uut = BuildEvaluateEngine( + fileSystemState, + operationGraph, + temporaryDirectory, + globalAllowedReadAccess, + globalAllowedWriteAccess); + + // Evaluate the build + uut.Evaluate(); + + // Verify expected logs + Assert::AreEqual( + std::vector({ + "DIAG: Build evaluation start", + "DIAG: Check for previous operation invocation", + "INFO: Input altered after last evaluate [C:/TestWorkingDirectory/Command.exe]", + "HIGH: TestCommand: 1", + "DIAG: Execute: [C:/TestWorkingDirectory/] ./Command.exe Arguments", + "DIAG: Allowed Read Access:", + "DIAG: Allowed Write Access:", + "DIAG: Build evaluation end", + }), + testListener->GetMessages(), + "Verify log messages match expected."); + + // Verify expected file system requests + Assert::AreEqual( + std::vector({}), + fileSystem->GetRequests(), + "Verify file system requests match expected."); + + // Verify expected process requests + Assert::AreEqual( + std::vector({ + "CreateDetourProcess: 1 [C:/TestWorkingDirectory/] ./Command.exe Arguments Environment [2] 1 AllowedRead [0] AllowedWrite [0]", + "ProcessStart: 1", + "WaitForExit: 1", + "GetStandardOutput: 1", + "GetStandardError: 1", + "GetExitCode: 1", + }), + detourProcessManager->GetRequests(), + "Verify detour process manager requests match expected."); + } + // [[Fact]] void Evaluate_OneOperation_Incremental_UpToDate() { @@ -472,8 +594,9 @@ namespace Soup::Core::UnitTests auto scopedTraceListener = ScopedTraceListenerRegister(testListener); // Setup the input/output files to be up to date - auto outputTime = CreateDateTime(2015, 5, 22, 9, 12); - auto inputTime = CreateDateTime(2015, 5, 22, 9, 11); + auto outputTime = std::chrono::sys_days(May/22/2015) + 9h + 12min; + auto inputTime = std::chrono::sys_days(May/22/2015) + 9h + 11min; + auto executableInputTime = std::chrono::sys_days(May/22/2015) + 9h + 10min; // Register the test file system auto fileSystem = std::make_shared(); @@ -483,10 +606,12 @@ namespace Soup::Core::UnitTests std::unordered_map({ { 1, Path("C:/TestWorkingDirectory/InputFile.in") }, { 2, Path("C:/TestWorkingDirectory/OutputFile.out") }, + { 3, Path("C:/TestWorkingDirectory/Command.exe") }, }), - std::unordered_map>({ + std::unordered_map>>({ { 1, inputTime }, { 2, outputTime }, + { 3, executableInputTime }, })); // Register the test process manager @@ -514,6 +639,7 @@ namespace Soup::Core::UnitTests { }, 1, true, + std::chrono::sys_days(May/22/2015) + 9h + 15min, { 1, }, { 2, }), }); diff --git a/Source/Client/Core/UnitTests/Build/BuildHistoryCheckerTests.h b/Source/Client/Core/UnitTests/Build/BuildHistoryCheckerTests.h index aeb0a4c69..48c71e393 100644 --- a/Source/Client/Core/UnitTests/Build/BuildHistoryCheckerTests.h +++ b/Source/Client/Core/UnitTests/Build/BuildHistoryCheckerTests.h @@ -22,7 +22,7 @@ namespace Soup::Core::UnitTests std::unordered_map({ { 1, Path("C:/Root/Output.bin") }, }), - std::unordered_map>({ + std::unordered_map>>({ { 1, std::nullopt }, })); @@ -64,7 +64,7 @@ namespace Soup::Core::UnitTests { 1, Path("C:/Root/Output.bin") }, { 2, Path("C:/Root/Input.cpp") }, }), - std::unordered_map>({ + std::unordered_map>>({ { 2, std::nullopt }, })); @@ -114,7 +114,7 @@ namespace Soup::Core::UnitTests { 1, Path("C:/Root/Output.bin") }, { 2, Path("C:/Root/Input.cpp") }, }), - std::unordered_map>({ + std::unordered_map>>({ { 1, std::nullopt }, { 2, std::nullopt }, })); @@ -155,7 +155,7 @@ namespace Soup::Core::UnitTests auto scopedFileSystem = ScopedFileSystemRegister(fileSystem); // Create the file state - auto outputTime = CreateDateTime(2015, 5, 22, 9, 12); + auto outputTime = std::chrono::sys_days(May/22/2015) + 9h + 12min; // Initialize the file system state auto fileSystemState = std::make_shared( @@ -164,7 +164,7 @@ namespace Soup::Core::UnitTests { 1, Path("C:/Root/Output.bin") }, { 2, Path("C:/Root/Input.cpp") }, }), - std::unordered_map>({ + std::unordered_map>>({ { 1, outputTime }, })); @@ -210,7 +210,7 @@ namespace Soup::Core::UnitTests auto scopedFileSystem = ScopedFileSystemRegister(fileSystem); // Create the file state - auto outputTime = CreateDateTime(2015, 5, 22, 9, 12); + auto outputTime = std::chrono::sys_days(May/22/2015) + 9h + 12min; // Initialize the file system state auto fileSystemState = std::make_shared( @@ -219,7 +219,7 @@ namespace Soup::Core::UnitTests { 1, Path("C:/Root/Output.bin") }, { 2, Path("C:/Root/Input.cpp") }, }), - std::unordered_map>({ + std::unordered_map>>({ { 1, outputTime }, { 2, std::nullopt }, })); @@ -260,8 +260,8 @@ namespace Soup::Core::UnitTests auto scopedTraceListener = ScopedTraceListenerRegister(testListener); // Create the file state - auto outputTime = CreateDateTime(2015, 5, 22, 9, 12); - auto inputTime = CreateDateTime(2015, 5, 22, 9, 13); + auto outputTime = std::chrono::sys_days(May/22/2015) + 9h + 12min; + auto inputTime = std::chrono::sys_days(May/22/2015) + 9h + 13min; // Initialize the file system state auto fileSystemState = std::make_shared( @@ -270,7 +270,7 @@ namespace Soup::Core::UnitTests { 1, Path("C:/Root/Output.bin") }, { 2, Path("C:/Root/Input.cpp") }, }), - std::unordered_map>({ + std::unordered_map>>({ { 1, outputTime }, { 2, inputTime }, })); @@ -307,8 +307,8 @@ namespace Soup::Core::UnitTests auto scopedTraceListener = ScopedTraceListenerRegister(testListener); // Create the file state - auto outputTime = CreateDateTime(2015, 5, 22, 9, 12); - auto inputTime = CreateDateTime(2015, 5, 22, 9, 11); + auto outputTime = std::chrono::sys_days(May/22/2015) + 9h + 12min; + auto inputTime = std::chrono::sys_days(May/22/2015) + 9h + 11min; // Initialize the file system state auto fileSystemState = std::make_shared( @@ -317,7 +317,7 @@ namespace Soup::Core::UnitTests { 1, Path("C:/Root/Output.bin") }, { 2, Path("C:/Root/Input.cpp") }, }), - std::unordered_map>({ + std::unordered_map>>({ { 1, outputTime }, { 2, inputTime }, })); @@ -352,8 +352,8 @@ namespace Soup::Core::UnitTests auto scopedTraceListener = ScopedTraceListenerRegister(testListener); // Create the file state - auto outputTime = CreateDateTime(2015, 5, 22, 9, 12); - auto inputTime = CreateDateTime(2015, 5, 22, 9, 11); + auto outputTime = std::chrono::sys_days(May/22/2015) + 9h + 12min; + auto inputTime = std::chrono::sys_days(May/22/2015) + 9h + 11min; // Initialize the file system state auto fileSystemState = std::make_shared( @@ -363,7 +363,7 @@ namespace Soup::Core::UnitTests { 2, Path("C:/Root/Input.cpp") }, { 3, Path("C:/Input.h") }, }), - std::unordered_map>({ + std::unordered_map>>({ { 1, outputTime }, { 2, inputTime }, { 3, inputTime }, diff --git a/Source/Client/Core/UnitTests/Build/FileSystemStateTests.h b/Source/Client/Core/UnitTests/Build/FileSystemStateTests.h index 3affe55eb..743f6549d 100644 --- a/Source/Client/Core/UnitTests/Build/FileSystemStateTests.h +++ b/Source/Client/Core/UnitTests/Build/FileSystemStateTests.h @@ -91,12 +91,12 @@ namespace Soup::Core::UnitTests std::unordered_map({ { 2, Path("C:/Root/DoStuff.exe") }, }), - std::unordered_map>({})); + std::unordered_map>>({})); auto lastWriteTime = uut.GetLastWriteTime(2); Assert::AreEqual( - std::optional(std::nullopt), + std::optional>(std::nullopt), lastWriteTime, "Verify last write time matches expected."); @@ -112,19 +112,22 @@ namespace Soup::Core::UnitTests // [[Fact]] void GetLastWriteTime_Found() { - auto setLastWriteTime = CreateDateTime(2015, 5, 22, 9, 11); + auto setLastWriteTime = std::chrono::sys_days(May/22/2015) + 9h + 11min; auto uut = FileSystemState( 10, std::unordered_map({ { 2, Path("C:/Root/DoStuff.exe") }, }), - std::unordered_map>({ + std::unordered_map>>({ { 2, setLastWriteTime }, })); auto lastWriteTime = uut.GetLastWriteTime(2); - Assert::AreEqual(std::optional(setLastWriteTime), lastWriteTime, "Verify last write time matches expected."); + Assert::AreEqual( + std::optional>(setLastWriteTime), + lastWriteTime, + "Verify last write time matches expected."); } // [[Fact]] diff --git a/Source/Client/Core/UnitTests/OperationGraph/OperationGraphManagerTests.h b/Source/Client/Core/UnitTests/OperationGraph/OperationGraphManagerTests.h index e0f72fe13..67940a513 100644 --- a/Source/Client/Core/UnitTests/OperationGraph/OperationGraphManagerTests.h +++ b/Source/Client/Core/UnitTests/OperationGraph/OperationGraphManagerTests.h @@ -94,9 +94,9 @@ namespace Soup::Core::UnitTests auto fileSystem = std::make_shared(); auto scopedFileSystem = ScopedFileSystemRegister(fileSystem); - auto binaryFileContent = std::vector( + auto binaryFileContent = std::vector( { - 'B', 'O', 'G', '\0', 0x03, 0x00, 0x00, 0x00, + 'B', 'O', 'G', '\0', 0x04, 0x00, 0x00, 0x00, 'F', 'I', 'S', '\0', 0x00, 0x00, 0x00, 0x00, 'R', 'O', 'P', '\0', 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, @@ -113,12 +113,13 @@ namespace Soup::Core::UnitTests 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x9b, 0x4f, 0xc9, 0xb4, 0xa6, 0xf1, 0xfc, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }); fileSystem->CreateMockFile( Path("TestFiles/SimpleOperationGraph/.soup/OperationGraph.bin"), - std::make_shared(std::stringstream(std::string(binaryFileContent.data(), binaryFileContent.size())))); + std::make_shared(std::stringstream(std::string((char*)binaryFileContent.data(), binaryFileContent.size())))); auto filePath = Path("TestFiles/SimpleOperationGraph/.soup/OperationGraph.bin"); auto fileSystemState = std::make_shared(); @@ -152,6 +153,7 @@ namespace Soup::Core::UnitTests { }, 1, false, + std::chrono::time_point_cast(std::chrono::time_point::min()), { }, { }), } @@ -208,6 +210,7 @@ namespace Soup::Core::UnitTests { }, 1, false, + std::chrono::time_point::min(), { }, { }), })); @@ -232,9 +235,9 @@ namespace Soup::Core::UnitTests "Verify messages match expected."); // Verify the file content - auto binaryFileContent = std::vector( + auto binaryFileContent = std::vector( { - 'B', 'O', 'G', '\0', 0x03, 0x00, 0x00, 0x00, + 'B', 'O', 'G', '\0', 0x04, 0x00, 0x00, 0x00, 'F', 'I', 'S', '\0', 0x00, 0x00, 0x00, 0x00, 'R', 'O', 'P', '\0', 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, @@ -251,12 +254,13 @@ namespace Soup::Core::UnitTests 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x9b, 0x4f, 0xc9, 0xb4, 0xa6, 0xf1, 0xfc, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }); auto mockFile = fileSystem->GetMockFile(Path("./TestFiles/.soup/OperationGraph.bin")); Assert::AreEqual( - std::string(binaryFileContent.data(), binaryFileContent.size()), + std::string((char*)binaryFileContent.data(), binaryFileContent.size()), mockFile->Content.str(), "Verify file content match expected."); } diff --git a/Source/Client/Core/UnitTests/OperationGraph/OperationGraphReaderTests.h b/Source/Client/Core/UnitTests/OperationGraph/OperationGraphReaderTests.h index 56519e887..b9432213c 100644 --- a/Source/Client/Core/UnitTests/OperationGraph/OperationGraphReaderTests.h +++ b/Source/Client/Core/UnitTests/OperationGraph/OperationGraphReaderTests.h @@ -3,6 +3,7 @@ // #pragma once +using namespace std::chrono; namespace Soup::Core::UnitTests { @@ -44,7 +45,7 @@ namespace Soup::Core::UnitTests { auto binaryFileContent = std::vector( { - 'B', 'O', 'G', '\0', 0x03, 0x00, 0x00, 0x00, + 'B', 'O', 'G', '\0', 0x04, 0x00, 0x00, 0x00, 'F', 'I', 'S', '2', }); auto content = std::stringstream(std::string(binaryFileContent.data(), binaryFileContent.size())); @@ -60,7 +61,7 @@ namespace Soup::Core::UnitTests { auto binaryFileContent = std::vector( { - 'B', 'O', 'G', '\0', 0x03, 0x00, 0x00, 0x00, + 'B', 'O', 'G', '\0', 0x04, 0x00, 0x00, 0x00, 'F', 'I', 'S', '\0', 0x00, 0x00, 0x00, 0x00, 'R', 'O', 'P', '2', }); @@ -77,7 +78,7 @@ namespace Soup::Core::UnitTests { auto binaryFileContent = std::vector( { - 'B', 'O', 'G', '\0', 0x03, 0x00, 0x00, 0x00, + 'B', 'O', 'G', '\0', 0x04, 0x00, 0x00, 0x00, 'F', 'I', 'S', '\0', 0x00, 0x00, 0x00, 0x00, 'R', 'O', 'P', '\0', 0x00, 0x00, 0x00, 0x00, 'O', 'P', 'S', '2', @@ -95,7 +96,7 @@ namespace Soup::Core::UnitTests { auto binaryFileContent = std::vector( { - 'B', 'O', 'G', '\0', 0x03, 0x00, 0x00, 0x00, + 'B', 'O', 'G', '\0', 0x04, 0x00, 0x00, 0x00, 'F', 'I', 'S', '\0', 0x00, 0x00, 0x00, 0x00, 'R', 'O', 'P', '\0', 0x00, 0x00, 0x00, 0x00, 'O', 'P', 'S', '\0', 0x00, 0x00, 0x00, 0x00, 'E', @@ -113,7 +114,7 @@ namespace Soup::Core::UnitTests { auto binaryFileContent = std::vector( { - 'B', 'O', 'G', '\0', 0x03, 0x00, 0x00, 0x00, + 'B', 'O', 'G', '\0', 0x04, 0x00, 0x00, 0x00, 'F', 'I', 'S', '\0', 0x00, 0x00, 0x00, 0x00, 'R', 'O', 'P', '\0', 0x00, 0x00, 0x00, 0x00, 'O', 'P', 'S', '\0', 0x00, 0x00, 0x00, 0x00, @@ -135,9 +136,9 @@ namespace Soup::Core::UnitTests // [[Fact]] void Deserialize_SingleSimple() { - auto binaryFileContent = std::vector( + auto binaryFileContent = std::vector( { - 'B', 'O', 'G', '\0', 0x03, 0x00, 0x00, 0x00, + 'B', 'O', 'G', '\0', 0x04, 0x00, 0x00, 0x00, 'F', 'I', 'S', '\0', 0x00, 0x00, 0x00, 0x00, 'R', 'O', 'P', '\0', 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, @@ -154,10 +155,11 @@ namespace Soup::Core::UnitTests 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x9b, 0x4f, 0xc9, 0xb4, 0xa6, 0xf1, 0xfc, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }); - auto content = std::stringstream(std::string(binaryFileContent.data(), binaryFileContent.size())); + auto content = std::stringstream(std::string((char*)binaryFileContent.data(), binaryFileContent.size())); auto actual = OperationGraphReader::Deserialize(content); @@ -183,6 +185,7 @@ namespace Soup::Core::UnitTests { }, 1, false, + std::chrono::time_point_cast(std::chrono::time_point::min()), { }, { }), } @@ -194,9 +197,9 @@ namespace Soup::Core::UnitTests // [[Fact]] void Deserialize_SingleComplex() { - auto binaryFileContent = std::vector( + auto binaryFileContent = std::vector( { - 'B', 'O', 'G', '\0', 0x03, 0x00, 0x00, 0x00, + 'B', 'O', 'G', '\0', 0x04, 0x00, 0x00, 0x00, 'F', 'I', 'S', '\0', 0x00, 0x00, 0x00, 0x00, 'R', 'O', 'P', '\0', 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, @@ -213,10 +216,11 @@ namespace Soup::Core::UnitTests 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xf1, 0x7d, 0xde, 0xbc, 0xf3, 0x39, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, }); - auto content = std::stringstream(std::string(binaryFileContent.data(), binaryFileContent.size())); + auto content = std::stringstream(std::string((char*)binaryFileContent.data(), binaryFileContent.size())); auto actual = OperationGraphReader::Deserialize(content); @@ -242,6 +246,7 @@ namespace Soup::Core::UnitTests { }, 1, true, + std::chrono::sys_days(March/5/2020) + 12h + 35min + 34s + 1ms, { 1, 3, }, { 2, 4, }), } @@ -253,9 +258,9 @@ namespace Soup::Core::UnitTests // [[Fact]] void Deserialize_Multiple() { - auto binaryFileContent = std::vector( + auto binaryFileContent = std::vector( { - 'B', 'O', 'G', '\0', 0x03, 0x00, 0x00, 0x00, + 'B', 'O', 'G', '\0', 0x04, 0x00, 0x00, 0x00, 'F', 'I', 'S', '\0', 0x00, 0x00, 0x00, 0x00, 'R', 'O', 'P', '\0', 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, @@ -272,6 +277,7 @@ namespace Soup::Core::UnitTests 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xf1, 0x7d, 0xde, 0xbc, 0xf3, 0x39, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, @@ -286,58 +292,64 @@ namespace Soup::Core::UnitTests 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x58, 0xba, 0xdf, 0xbc, 0xf3, 0x39, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, }); - auto content = std::stringstream(std::string(binaryFileContent.data(), binaryFileContent.size())); + auto content = std::stringstream(std::string((char*)binaryFileContent.data(), binaryFileContent.size())); auto actual = OperationGraphReader::Deserialize(content); + auto expected = std::unordered_map( + { + { + 5, + OperationInfo( + 5, + "TestOperation1", + CommandInfo( + Path("C:/Root/"), + Path("DoStuff1.exe"), + "arg1 arg2"), + { 1, }, + { 2, }, + { }, + { }, + { }, + 2, + true, + std::chrono::sys_days(March / 5 / 2020) + 12h + 35min + 34s + 1ms, + { 1, 3, }, + { 2, 4, }), + }, + { + 6, + OperationInfo( + 6, + "TestOperation2", + CommandInfo( + Path("C:/Root/"), + Path("DoStuff2.exe"), + "arg3 arg4"), + { 5, }, + { 6, }, + { 5, }, + { }, + { }, + 1, + true, + std::chrono::sys_days(March / 5 / 2020) + 12h + 36min + 55s, + { 5, 7, }, + { 6, 8, }), + }, + }); + Assert::AreEqual( std::vector({ 6, }), actual.GetRootOperationIds(), "Verify root operation ids match expected."); Assert::AreEqual( - std::unordered_map({ - { - 5, - OperationInfo( - 5, - "TestOperation1", - CommandInfo( - Path("C:/Root/"), - Path("DoStuff1.exe"), - "arg1 arg2"), - { 1, }, - { 2, }, - { }, - { }, - { }, - 2, - true, - { 1, 3, }, - { 2, 4, }), - }, - { - 6, - OperationInfo( - 6, - "TestOperation2", - CommandInfo( - Path("C:/Root/"), - Path("DoStuff2.exe"), - "arg3 arg4"), - { 5, }, - { 6, }, - { 5, }, - { }, - { }, - 1, - true, - { 5, 7, }, - { 6, 8, }), - }, - }), + expected, actual.GetOperations(), "Verify operations match expected."); } diff --git a/Source/Client/Core/UnitTests/OperationGraph/OperationGraphTests.h b/Source/Client/Core/UnitTests/OperationGraph/OperationGraphTests.h index 6586389fe..91d14dce1 100644 --- a/Source/Client/Core/UnitTests/OperationGraph/OperationGraphTests.h +++ b/Source/Client/Core/UnitTests/OperationGraph/OperationGraphTests.h @@ -49,6 +49,7 @@ namespace Soup::Core::UnitTests { }, 1, false, + std::chrono::time_point::min(), { }, { }), })); @@ -77,6 +78,7 @@ namespace Soup::Core::UnitTests { }, 1, false, + std::chrono::time_point::min(), { }, { }), }, @@ -108,6 +110,7 @@ namespace Soup::Core::UnitTests { }, 1, false, + std::chrono::time_point::min(), { }, { }), })); @@ -177,6 +180,7 @@ namespace Soup::Core::UnitTests { }, 1, false, + std::chrono::time_point::min(), { }, { }), })); @@ -206,6 +210,7 @@ namespace Soup::Core::UnitTests { }, 1, false, + std::chrono::time_point::min(), { }, { }), *operationInfo, @@ -253,6 +258,7 @@ namespace Soup::Core::UnitTests { }, 1, false, + std::chrono::time_point::min(), { }, { }), })); @@ -274,6 +280,7 @@ namespace Soup::Core::UnitTests { }, 1, false, + std::chrono::time_point::min(), { }, { }), operationInfo, @@ -305,6 +312,7 @@ namespace Soup::Core::UnitTests { }, 1, false, + std::chrono::time_point::min(), { }, { })); @@ -326,6 +334,7 @@ namespace Soup::Core::UnitTests { }, 1, false, + std::chrono::time_point::min(), { }, { }), } diff --git a/Source/Client/Core/UnitTests/OperationGraph/OperationGraphWriterTests.h b/Source/Client/Core/UnitTests/OperationGraph/OperationGraphWriterTests.h index c504a3643..ee4e20d6d 100644 --- a/Source/Client/Core/UnitTests/OperationGraph/OperationGraphWriterTests.h +++ b/Source/Client/Core/UnitTests/OperationGraph/OperationGraphWriterTests.h @@ -3,6 +3,7 @@ // #pragma once +using namespace std::chrono; namespace Soup::Core::UnitTests { @@ -17,15 +18,15 @@ namespace Soup::Core::UnitTests OperationGraphWriter::Serialize(operationGraph, content); - auto binaryFileContent = std::vector( + auto binaryFileContent = std::vector( { - 'B', 'O', 'G', '\0', 0x03, 0x00, 0x00, 0x00, + 'B', 'O', 'G', '\0', 0x04, 0x00, 0x00, 0x00, 'F', 'I', 'S', '\0', 0x00, 0x00, 0x00, 0x00, 'R', 'O', 'P', '\0', 0x00, 0x00, 0x00, 0x00, 'O', 'P', 'S', '\0', 0x00, 0x00, 0x00, 0x00, }); Assert::AreEqual( - std::string(binaryFileContent.data(), binaryFileContent.size()), + std::string((char*)binaryFileContent.data(), binaryFileContent.size()), content.str(), "Verify file content match expected."); } @@ -51,6 +52,7 @@ namespace Soup::Core::UnitTests { }, 1, false, + std::chrono::time_point::min(), { }, { }), })); @@ -58,9 +60,9 @@ namespace Soup::Core::UnitTests OperationGraphWriter::Serialize(operationGraph, content); - auto binaryFileContent = std::vector( + auto binaryFileContent = std::vector( { - 'B', 'O', 'G', '\0', 0x03, 0x00, 0x00, 0x00, + 'B', 'O', 'G', '\0', 0x04, 0x00, 0x00, 0x00, 'F', 'I', 'S', '\0', 0x00, 0x00, 0x00, 0x00, 'R', 'O', 'P', '\0', 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, @@ -77,11 +79,13 @@ namespace Soup::Core::UnitTests 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x9b, 0x4f, 0xc9, 0xb4, 0xa6, 0xf1, 0xfc, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }); + Assert::AreEqual( - std::string(binaryFileContent.data(), binaryFileContent.size()), + std::string((char*)binaryFileContent.data(), binaryFileContent.size()), content.str(), "Verify file content match expected."); } @@ -107,6 +111,7 @@ namespace Soup::Core::UnitTests { }, 1, true, + std::chrono::sys_days(March/5/2020) + 12h + 35min + 34s + 1ms, { 1, 3, }, { 2, 4, }), })); @@ -114,9 +119,9 @@ namespace Soup::Core::UnitTests OperationGraphWriter::Serialize(operationGraph, content); - auto binaryFileContent = std::vector( + auto binaryFileContent = std::vector( { - 'B', 'O', 'G', '\0', 0x03, 0x00, 0x00, 0x00, + 'B', 'O', 'G', '\0', 0x04, 0x00, 0x00, 0x00, 'F', 'I', 'S', '\0', 0x00, 0x00, 0x00, 0x00, 'R', 'O', 'P', '\0', 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, @@ -133,11 +138,12 @@ namespace Soup::Core::UnitTests 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xf1, 0x7d, 0xde, 0xbc, 0xf3, 0x39, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, }); Assert::AreEqual( - std::string(binaryFileContent.data(), binaryFileContent.size()), + std::string((char*)binaryFileContent.data(), binaryFileContent.size()), content.str(), "Verify file content match expected."); } @@ -163,6 +169,7 @@ namespace Soup::Core::UnitTests { }, 2, true, + std::chrono::sys_days(March/5/2020) + 12h + 35min + 34s + 1ms, { 1, 3, }, { 2, 4, }), OperationInfo( @@ -179,6 +186,7 @@ namespace Soup::Core::UnitTests { }, 1, true, + std::chrono::sys_days(March/5/2020) + 12h + 36min + 55s, { 5, 7, }, { 6, 8, }), })); @@ -186,9 +194,9 @@ namespace Soup::Core::UnitTests OperationGraphWriter::Serialize(operationGraph, content); - auto binaryFileContent = std::vector( + auto binaryFileContent = std::vector( { - 'B', 'O', 'G', '\0', 0x03, 0x00, 0x00, 0x00, + 'B', 'O', 'G', '\0', 0x04, 0x00, 0x00, 0x00, 'F', 'I', 'S', '\0', 0x00, 0x00, 0x00, 0x00, 'R', 'O', 'P', '\0', 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, @@ -205,6 +213,7 @@ namespace Soup::Core::UnitTests 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xf1, 0x7d, 0xde, 0xbc, 0xf3, 0x39, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, @@ -219,12 +228,13 @@ namespace Soup::Core::UnitTests 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x58, 0xba, 0xdf, 0xbc, 0xf3, 0x39, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, }); Assert::AreEqual( - std::string(binaryFileContent.data(), binaryFileContent.size()), + std::string((char*)binaryFileContent.data(), binaryFileContent.size()), content.str(), "Verify file content match expected."); } diff --git a/Source/Client/Core/UnitTests/Utils/TestHelpers.h b/Source/Client/Core/UnitTests/Utils/TestHelpers.h index 38ea33970..04c62ffe8 100644 --- a/Source/Client/Core/UnitTests/Utils/TestHelpers.h +++ b/Source/Client/Core/UnitTests/Utils/TestHelpers.h @@ -3,25 +3,10 @@ // #pragma once +using namespace std::chrono; +using namespace std::chrono_literals; namespace Soup::Core::UnitTests { - std::time_t CreateDateTime(int year, int month, int day, int hour, int minutes) - { - std::tm timeInfo = std::tm(); - // Year is offset from 1900 - timeInfo.tm_year = year - 1900; - timeInfo.tm_mon = month; - timeInfo.tm_mday = day; - timeInfo.tm_hour = hour; - timeInfo.tm_min = minutes; - - std::time_t time = std::mktime(&timeInfo); - - long timezone; - _get_timezone(&timezone); - - return time - timezone; - } } \ No newline at end of file diff --git a/Source/Client/Core/UnitTests/gen/Build/BuildEvaluateEngineTests.gen.h b/Source/Client/Core/UnitTests/gen/Build/BuildEvaluateEngineTests.gen.h index dbd76ed67..e968a4bb1 100644 --- a/Source/Client/Core/UnitTests/gen/Build/BuildEvaluateEngineTests.gen.h +++ b/Source/Client/Core/UnitTests/gen/Build/BuildEvaluateEngineTests.gen.h @@ -11,6 +11,7 @@ TestState RunBuildEvaluateEngineTests() state += Soup::Test::RunTest(className, "Evaluate_OneOperation_Incremental_MissingFileInfo", [&testClass]() { testClass->Evaluate_OneOperation_Incremental_MissingFileInfo(); }); state += Soup::Test::RunTest(className, "Evaluate_OneOperation_Incremental_MissingTargetFile", [&testClass]() { testClass->Evaluate_OneOperation_Incremental_MissingTargetFile(); }); state += Soup::Test::RunTest(className, "Evaluate_OneOperation_Incremental_OutOfDate", [&testClass]() { testClass->Evaluate_OneOperation_Incremental_OutOfDate(); }); + state += Soup::Test::RunTest(className, "Evaluate_OneOperation_Incremental_Executable_OutOfDate", [&testClass]() { testClass->Evaluate_OneOperation_Incremental_Executable_OutOfDate(); }); state += Soup::Test::RunTest(className, "Evaluate_OneOperation_Incremental_UpToDate", [&testClass]() { testClass->Evaluate_OneOperation_Incremental_UpToDate(); }); return state; diff --git a/Source/Client/Core/UnitTests/gen/Main.cpp b/Source/Client/Core/UnitTests/gen/Main.cpp index 5b0d08395..af3072766 100644 --- a/Source/Client/Core/UnitTests/gen/Main.cpp +++ b/Source/Client/Core/UnitTests/gen/Main.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include diff --git a/Source/GenerateSharp/Extensions/Cpp/Compiler/MSVC.UnitTests/CompilerArgumentBuilderUnitTests.cs b/Source/GenerateSharp/Extensions/Cpp/Compiler/MSVC.UnitTests/CompilerArgumentBuilderUnitTests.cs index 9a074e3b2..b289a16e2 100644 --- a/Source/GenerateSharp/Extensions/Cpp/Compiler/MSVC.UnitTests/CompilerArgumentBuilderUnitTests.cs +++ b/Source/GenerateSharp/Extensions/Cpp/Compiler/MSVC.UnitTests/CompilerArgumentBuilderUnitTests.cs @@ -3,6 +3,7 @@ // using Opal; +using System; using System.Collections.Generic; using Xunit; diff --git a/Source/GenerateSharp/Runtime/OperationGraph/OperationInfo.cs b/Source/GenerateSharp/Runtime/OperationGraph/OperationInfo.cs index 05da8aee7..04847c3b1 100644 --- a/Source/GenerateSharp/Runtime/OperationGraph/OperationInfo.cs +++ b/Source/GenerateSharp/Runtime/OperationGraph/OperationInfo.cs @@ -90,6 +90,7 @@ public OperationInfo() : new List(), 0, false, + DateTime.MinValue, new List(), new List()) { @@ -117,6 +118,7 @@ public OperationInfo( new List(), 0, false, + DateTime.MinValue, new List(), new List()) { @@ -136,6 +138,7 @@ public OperationInfo( IList children, uint dependencyCount, bool wasSuccessfulRun, + DateTime evaluateTime, IList observedInput, IList observedOutput) { @@ -149,6 +152,7 @@ public OperationInfo( Children = children; DependencyCount = dependencyCount; WasSuccessfulRun = wasSuccessfulRun; + EvaluateTime = evaluateTime; ObservedInput = observedInput; ObservedOutput = observedOutput; } @@ -168,6 +172,7 @@ public bool Equals(OperationInfo? rhs) Enumerable.SequenceEqual(Children, rhs.Children) && DependencyCount == rhs.DependencyCount && WasSuccessfulRun == rhs.WasSuccessfulRun && + EvaluateTime == rhs.EvaluateTime && Enumerable.SequenceEqual(ObservedInput, rhs.ObservedInput) && Enumerable.SequenceEqual(ObservedOutput, rhs.ObservedOutput); @@ -206,7 +211,9 @@ public override int GetHashCode() public IList Children { get; init; } public uint DependencyCount { get; set; } public bool WasSuccessfulRun { get; init; } + public DateTime EvaluateTime { get; init; } public IList ObservedInput { get; init; } public IList ObservedOutput { get; init; } + } } \ No newline at end of file diff --git a/Source/GenerateSharp/Utilities/OperationGraph/OperationGraphReader.cs b/Source/GenerateSharp/Utilities/OperationGraph/OperationGraphReader.cs index 1d4e5ad30..7b559c335 100644 --- a/Source/GenerateSharp/Utilities/OperationGraph/OperationGraphReader.cs +++ b/Source/GenerateSharp/Utilities/OperationGraph/OperationGraphReader.cs @@ -15,7 +15,7 @@ namespace Soup.Build.Utilities internal static class OperationGraphReader { // Binary Operation Graph file format - private static uint FileVersion => 3; + private static uint FileVersion => 4; public static OperationGraph Deserialize(System.IO.BinaryReader reader) { @@ -100,46 +100,49 @@ public static OperationGraph Deserialize(System.IO.BinaryReader reader) private static OperationInfo ReadOperationInfo(System.IO.BinaryReader reader) { - // Write out the operation id + // Read the operation id var id = new OperationId(reader.ReadUInt32()); - // Write the operation title + // Read the operation title var title = ReadString(reader); - // Write the command working directory + // Read the command working directory var workingDirectory = ReadString(reader); - // Write the command executable + // Read the command executable var executable = ReadString(reader); - // Write the command arguments + // Read the command arguments var arguments = ReadString(reader); - // Write out the declared input files + // Read the declared input files var declaredInput = ReadFileIdList(reader); - // Write out the declared output files + // Read the declared output files var declaredOutput = ReadFileIdList(reader); - // Write out the read access list + // Read the read access list var readAccess = ReadFileIdList(reader); - // Write out the write access list + // Read the write access list var writeAccess = ReadFileIdList(reader); - // Write out the child operation ids + // Read the child operation ids var children = ReadOperationIdList(reader); - // Write out the dependency count + // Read the dependency count var dependecyCount = reader.ReadUInt32(); - // Write out the value indicating if there was a successful run + // Read the value indicating if there was a successful run var wasSuccessfulRun = ReadBoolean(reader); - // Write out the observed input files + // Read the utc tick since January 1, 0001 at 00:00:00.000 in the Gregorian calendar + var evaluateTime = new DateTime(reader.ReadInt64(), DateTimeKind.Utc); + + // Read the observed input files var observedInput = ReadFileIdList(reader); - // Write out the observed output files + // Read the observed output files var observedOutput = ReadFileIdList(reader); return new OperationInfo( @@ -156,6 +159,7 @@ private static OperationInfo ReadOperationInfo(System.IO.BinaryReader reader) children, dependecyCount, wasSuccessfulRun, + evaluateTime, observedInput, observedOutput); } diff --git a/Source/GenerateSharp/Utilities/OperationGraph/OperationGraphWriter.cs b/Source/GenerateSharp/Utilities/OperationGraph/OperationGraphWriter.cs index d106aba31..8e7432459 100644 --- a/Source/GenerateSharp/Utilities/OperationGraph/OperationGraphWriter.cs +++ b/Source/GenerateSharp/Utilities/OperationGraph/OperationGraphWriter.cs @@ -3,6 +3,7 @@ // using Soup.Build.Runtime; +using System; using System.Collections.Generic; namespace Soup.Build.Utilities @@ -13,7 +14,7 @@ namespace Soup.Build.Utilities internal static class OperationGraphWriter { // Binary Operation graph file format - private static uint FileVersion => 3; + private static uint FileVersion => 4; public static void Serialize(OperationGraph state, System.IO.BinaryWriter writer) { @@ -84,6 +85,10 @@ private static void WriteOperationInfo(System.IO.BinaryWriter writer, OperationI // Write out the value indicating if there was a successful run WriteValue(writer, operation.WasSuccessfulRun); + // Write out the utc milliseconds since January 1, 0001 at 00:00:00.000 in the Gregorian calendar + var evaluateTimeMilliseconds = operation.EvaluateTime.ToUniversalTime().Ticks / 10; + writer.Write(evaluateTimeMilliseconds); + // Write out the observed input files WriteValues(writer, operation.ObservedInput); diff --git a/Source/Installer/SoupInstaller/Setup.cs b/Source/Installer/SoupInstaller/Setup.cs index 31db96caf..89e02bdd2 100644 --- a/Source/Installer/SoupInstaller/Setup.cs +++ b/Source/Installer/SoupInstaller/Setup.cs @@ -65,7 +65,7 @@ static public void Main() }; // Upgrade values - project.Version = new Version(0, 17, 1); + project.Version = new Version(0, 17, 3); Compiler.BuildMsi(project); } diff --git a/Source/Tools/Mkdir/Main.cpp b/Source/Tools/Mkdir/Main.cpp index 441fa7e0e..bf80df413 100644 --- a/Source/Tools/Mkdir/Main.cpp +++ b/Source/Tools/Mkdir/Main.cpp @@ -21,9 +21,48 @@ int main(int argc, char** argv) { auto errorCode = GetLastError(); if (errorCode == ERROR_ALREADY_EXISTS) + { std::cout << "Directory already exists" << std::endl; + + // Get the current system time + SYSTEMTIME systemTime; + GetSystemTime(&systemTime); + + // Convert time file time + FILETIME fileTime; + if (!SystemTimeToFileTime(&systemTime, &fileTime)) + { + throw std::runtime_error("Failed to convert from system time to file time"); + } + + // Open the directory handle to write + auto directoryHandle = CreateFileA( + directory.data(), + GENERIC_WRITE, + FILE_SHARE_WRITE | FILE_SHARE_READ, + nullptr, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + 0); + if (directoryHandle == INVALID_HANDLE_VALUE) + { + auto createErrorCode = GetLastError(); + std::cout << createErrorCode << std::endl; + throw std::runtime_error("Failed to open directory handle to write"); + } + + // Mark the directory as updated + if (!SetFileTime(directoryHandle, nullptr, nullptr, &fileTime)) + { + throw std::runtime_error("Set directory time failed"); + } + + CloseHandle(directoryHandle); + } else + { throw std::runtime_error("Create directory failed"); + } } else {