From 4ae68311819d0a7fa5b47421e9049e57a847d5cc Mon Sep 17 00:00:00 2001 From: Simeon Armenchev Date: Wed, 31 Jul 2024 23:36:27 +0300 Subject: [PATCH] unit tests --- packages/mcl/src/src/mcl/commands/ci_matrix.d | 141 ++++++++++++------ .../mcl/src/src/mcl/commands/shard_matrix.d | 30 ++-- packages/mcl/src/src/mcl/utils/array.d | 25 ++-- packages/mcl/src/src/mcl/utils/cachix.d | 4 +- packages/mcl/src/src/mcl/utils/coda.d | 34 ++--- packages/mcl/src/src/mcl/utils/env.d | 11 +- packages/mcl/src/src/mcl/utils/fetch.d | 12 +- packages/mcl/src/src/mcl/utils/json.d | 63 ++++---- packages/mcl/src/src/mcl/utils/nix.d | 24 ++- packages/mcl/src/src/mcl/utils/path.d | 11 +- packages/mcl/src/src/mcl/utils/process.d | 8 +- packages/mcl/src/src/mcl/utils/string.d | 96 +++++++----- 12 files changed, 282 insertions(+), 177 deletions(-) diff --git a/packages/mcl/src/src/mcl/commands/ci_matrix.d b/packages/mcl/src/src/mcl/commands/ci_matrix.d index 1eada418..b7cbc952 100755 --- a/packages/mcl/src/src/mcl/commands/ci_matrix.d +++ b/packages/mcl/src/src/mcl/commands/ci_matrix.d @@ -61,12 +61,18 @@ GitHubOS getGHOS(string os) return os in osMap ? osMap[os] : GitHubOS.selfHosted; } +void assertGHOS(string input, GitHubOS expected) +{ + auto actual = getGHOS(input); + assert(actual == expected, fmt("getGHOS(\"%s\") should return %s, but returned %s", input, expected, actual)); +} + @("getGHOS") unittest { - assert(getGHOS("ubuntu-latest") == GitHubOS.ubuntuLatest); - assert(getGHOS("macos-14") == GitHubOS.macos14); - assert(getGHOS("crazyos-inator-2000") == GitHubOS.selfHosted); + assertGHOS("ubuntu-latest", GitHubOS.ubuntuLatest); + assertGHOS("macos-14", GitHubOS.macos14); + assertGHOS("crazyos-inator-2000", GitHubOS.selfHosted); } immutable SupportedSystem[string] systemMap; @@ -91,13 +97,19 @@ SupportedSystem getSystem(string system) return system in systemMap ? systemMap[system] : SupportedSystem.x86_64_linux; } +void assertSystem(string input, SupportedSystem expected) +{ + auto actual = getSystem(input); + assert(actual == expected, fmt("getSystem(\"%s\") should return %s, but returned %s", input, expected, actual)); +} + @("getSystem") unittest { - assert(getSystem("x86_64-linux") == SupportedSystem.x86_64_linux); - assert(getSystem("x86_64-darwin") == SupportedSystem.x86_64_darwin); - assert(getSystem("aarch64-darwin") == SupportedSystem.aarch64_darwin); - assert(getSystem("bender-bending-rodriguez-os") == SupportedSystem.x86_64_linux); + assertSystem("x86_64-linux", SupportedSystem.x86_64_linux); + assertSystem("x86_64-darwin", SupportedSystem.x86_64_darwin); + assertSystem("aarch64-darwin", SupportedSystem.aarch64_darwin); + assertSystem("bender-bending-rodriguez-os", SupportedSystem.x86_64_linux); } struct Package @@ -228,12 +240,18 @@ struct Params GitHubOS systemToGHPlatform(SupportedSystem os) => os == SupportedSystem.x86_64_linux ? GitHubOS.selfHosted : GitHubOS.macos14; +void assertGHPlatform(SupportedSystem system, GitHubOS expected) +{ + auto actual = systemToGHPlatform(system); + assert(actual == expected, fmt("`systemToGHPlatform(%s)` should return `%s`, but returned `%s`", system, expected, actual)); +} + @("systemToGHPlatform") unittest { - assert(systemToGHPlatform(SupportedSystem.x86_64_linux) == GitHubOS.selfHosted); - assert(systemToGHPlatform(SupportedSystem.x86_64_darwin) == GitHubOS.macos14); - assert(systemToGHPlatform(SupportedSystem.aarch64_darwin) == GitHubOS.macos14); + assertGHPlatform(SupportedSystem.x86_64_linux, GitHubOS.selfHosted); + assertGHPlatform(SupportedSystem.x86_64_darwin, GitHubOS.macos14); + assertGHPlatform(SupportedSystem.aarch64_darwin, GitHubOS.macos14); } static immutable string[] uselessWarnings = @@ -370,7 +388,9 @@ int getNixEvalWorkerCount() @("getNixEvalWorkerCount") unittest { - assert(getNixEvalWorkerCount() == (threadsPerCPU() < MAX_WORKERS ? threadsPerCPU() : MAX_WORKERS)); + auto actual = getNixEvalWorkerCount(); + assert(actual == (threadsPerCPU() < MAX_WORKERS ? threadsPerCPU() : MAX_WORKERS), + "getNixEvalWorkerCount() should return the number of threads per CPU if it is less than MAX_WORKERS, otherwise it should return MAX_WORKERS, but returned %s".fmt(actual)); } string[] meminfo; @@ -402,11 +422,13 @@ unittest { // Test when params.maxMemory is 0 params.maxMemory = 0; - assert(getAvailableMemoryMB() > 0); + auto actual = getAvailableMemoryMB(); + assert(actual > 0, "getAvailableMemoryMB() should return a value greater than 0, but returned %s".fmt(actual)); // Test when params.maxMemory is not 0 params.maxMemory = 1024; - assert(getAvailableMemoryMB() == 1024); + actual = getAvailableMemoryMB(); + assert(actual == 1024, "getAvailableMemoryMB() should return 1024, but returned %s".fmt(actual)); } void saveCachixDeploySpec(Package[] packages) @@ -429,8 +451,14 @@ unittest createResultDirs(); saveCachixDeploySpec(cast(Package[]) testPackageArray); JSONValue deploySpec = parseJSON((resultDir() ~ "/cachix-deploy-spec.json").readText); - assert(testPackageArray[1].name == deploySpec[0]["package"].str); - assert(testPackageArray[1].output == deploySpec[0]["out"].str); + string testPackageName = testPackageArray[1].name; + string deploySpecName = deploySpec[0]["package"].str; + string testPackageOutput = testPackageArray[1].output; + string deploySpecOutput = deploySpec[0]["out"].str; + assert(testPackageName == deploySpecName, + "The name of the package should be %s, but was %s".fmt(testPackageName, deploySpecName)); + assert(testPackageOutput == deploySpecOutput, + "The output of the package should be %s, but was %s".fmt(testPackageOutput, deploySpecOutput)); } void saveGHCIMatrix(Package[] packages) @@ -463,7 +491,10 @@ unittest : "matrix-post.json")).readText); foreach (i, pkg; testPackageArray) { - assert(pkg.name == matrix["include"][i]["name"].str); + string pkgName = pkg.name; + string matrixName = matrix["include"][i]["name"].str; + assert(pkgName == matrixName, + "The name of the package should be %s, but was %s".fmt(pkgName, matrixName)); } } @@ -504,10 +535,14 @@ unittest string comment = (rootDir() ~ "comment.md").readText; foreach (pkg; testSummaryTableEntryArray) { - assert(comment.indexOf(pkg.name) != -1); - assert(comment.indexOf(pkg.x86_64.linux) != -1); - assert(comment.indexOf(pkg.x86_64.darwin) != -1); - assert(comment.indexOf(pkg.aarch64.darwin) != -1); + assert(comment.indexOf(pkg.name) != -1, + "The comment should contain the package name %s, the comment is:\n%s".fmt(pkg.name, comment)); + assert(comment.indexOf(pkg.x86_64.linux) != -1, + "The comment should contain the x86_64 linux status %s, the comment is:\n%s".fmt(pkg.x86_64.linux, comment)); + assert(comment.indexOf(pkg.x86_64.darwin) != -1, + "The comment should contain the x86_64 darwin status %s, the comment is:\n%s".fmt(pkg.x86_64.darwin, comment)); + assert(comment.indexOf(pkg.aarch64.darwin) != -1, + "The comment should contain the aarch64 darwin status %s, the comment is:\n%s".fmt(pkg.aarch64.darwin, comment)); } } @@ -561,30 +596,35 @@ SummaryTableEntry createSummaryTableEntry(Package[] group) return entry; } +void assertNixTable(SummaryTableEntry[] tableSummary, immutable(Package[]) testPackageArray, int index, + string expectedLinuxStatus = "[" ~ Status.cached ~ "](https://testPackage.com)", string expectedDarwinStatus = Status.notSupported, string expectedAarch64Status = Status.notSupported) +{ + string actualName = tableSummary[index].name; + string expectedName = testPackageArray[index].name; + assert(actualName == expectedName, fmt("Expected name to be %s, but got %s", expectedName, actualName)); + + string actualLinuxStatus = tableSummary[index].x86_64.linux; + assert(actualLinuxStatus == expectedLinuxStatus, fmt("Expected Linux status to be %s, but got %s", expectedLinuxStatus, actualLinuxStatus)); + + string actualDarwinStatus = tableSummary[index].x86_64.darwin; + assert(actualDarwinStatus == expectedDarwinStatus, fmt("Expected Darwin status to be %s, but got %s", expectedDarwinStatus, actualDarwinStatus)); + + string actualAarch64Status = tableSummary[index].aarch64.darwin; + assert(actualAarch64Status == expectedAarch64Status, fmt("Expected Aarch64 Darwin status to be %s, but got %s", expectedAarch64Status, actualAarch64Status)); +} + @("convertNixEvalToTableSummary/getStatus") unittest { auto tableSummary = convertNixEvalToTableSummary(cast(Package[]) testPackageArray); - assert(tableSummary[0].name == testPackageArray[0].name); - assert(tableSummary[0].x86_64.linux == "[" ~ Status.cached ~ "](https://testPackage.com)"); - assert(tableSummary[0].x86_64.darwin == Status.notSupported); - assert(tableSummary[0].aarch64.darwin == Status.notSupported); - assert(tableSummary[1].name == testPackageArray[1].name); - assert(tableSummary[1].x86_64.linux == Status.notSupported); - assert(tableSummary[1].x86_64.darwin == Status.notSupported); - assert(tableSummary[1].aarch64.darwin == Status.buildFailed); + assertNixTable(tableSummary, testPackageArray, 0); + assertNixTable(tableSummary, testPackageArray, 1, Status.notSupported, Status.notSupported, Status.buildFailed); params.isInitial = true; tableSummary = convertNixEvalToTableSummary(cast(Package[]) testPackageArray); params.isInitial = false; - assert(tableSummary[0].name == testPackageArray[0].name); - assert(tableSummary[0].x86_64.linux == "[" ~ Status.cached ~ "](https://testPackage.com)"); - assert(tableSummary[0].x86_64.darwin == Status.notSupported); - assert(tableSummary[0].aarch64.darwin == Status.notSupported); - assert(tableSummary[1].name == testPackageArray[1].name); - assert(tableSummary[1].x86_64.linux == Status.notSupported); - assert(tableSummary[1].x86_64.darwin == Status.notSupported); - assert(tableSummary[1].aarch64.darwin == Status.building); + assertNixTable(tableSummary, testPackageArray, 0); + assertNixTable(tableSummary, testPackageArray, 1, Status.notSupported, Status.notSupported, Status.building); } void printTableForCacheStatus(Package[] packages) @@ -637,12 +677,11 @@ unittest cacheUrl: nixosCacheEndpoint ~ storePathHash ~ ".narinfo", }; - assert(!testPackage.isCached); - assert(checkPackage(testPackage).isCached); + assert(checkPackage(testPackage).isCached, "Package %s should be cached".fmt(testPackage.cacheUrl)); testPackage.cacheUrl = nixosCacheEndpoint ~ "nonexistent.narinfo"; - assert(!checkPackage(testPackage).isCached); + assert(!checkPackage(testPackage).isCached, "Package %s should not be cached".fmt(testPackage.cacheUrl)); } Package[] getPrecalcMatrix() @@ -674,13 +713,23 @@ unittest string precalcMatrixStr = "{\"include\": [{\"name\": \"test\", \"allowedToFail\": false, \"attrPath\": \"test\", \"cacheUrl\": \"url\", \"isCached\": true, \"os\": \"linux\", \"system\": \"x86_64-linux\", \"output\": \"output\"}]}"; params.precalcMatrix = precalcMatrixStr; auto packages = getPrecalcMatrix(); + Package testPackage = { + name: "test", + allowedToFail: false, + attrPath: "test", + cacheUrl: "url", + isCached: true, + os: GitHubOS.selfHosted, + system: SupportedSystem.x86_64_linux, + output: "output" + }; assert(packages.length == 1); - assert(packages[0].name == "test"); - assert(!packages[0].allowedToFail); - assert(packages[0].attrPath == "test"); - assert(packages[0].cacheUrl == "url"); + assert(packages[0].name == testPackage.name, "Expected %s, got %s".fmt(testPackage.name, packages[0].name)); + assert(!packages[0].allowedToFail, "Expected %s, got %s".fmt(testPackage.allowedToFail, packages[0].allowedToFail)); + assert(packages[0].attrPath == testPackage.attrPath, "Expected %s, got %s".fmt(testPackage.attrPath, packages[0].attrPath)); + assert(packages[0].cacheUrl == testPackage.cacheUrl, "Expected %s, got %s".fmt(testPackage.cacheUrl, packages[0].cacheUrl)); assert(packages[0].isCached); - assert(packages[0].os == GitHubOS.selfHosted); - assert(packages[0].system == SupportedSystem.x86_64_linux); - assert(packages[0].output == "output"); + assert(packages[0].os == testPackage.os, "Expected %s, got %s".fmt(testPackage.os, packages[0].os)); + assert(packages[0].system == testPackage.system, "Expected %s, got %s".fmt(testPackage.system, packages[0].system)); + assert(packages[0].output == testPackage.output, "Expected %s, got %s".fmt(testPackage.output, packages[0].output)); } diff --git a/packages/mcl/src/src/mcl/commands/shard_matrix.d b/packages/mcl/src/src/mcl/commands/shard_matrix.d index 20f02c95..f91f7083 100644 --- a/packages/mcl/src/src/mcl/commands/shard_matrix.d +++ b/packages/mcl/src/src/mcl/commands/shard_matrix.d @@ -5,6 +5,9 @@ import std.conv : to, parse; import std.stdio : writeln; import std.string : strip; import std.regex : matchFirst, regex; +import std.format : format; +import std.algorithm : each; +import std.parallelism : parallel; import mcl.utils.nix : nix; import mcl.utils.path : createResultDirs, resultDir, rootDir; @@ -64,15 +67,21 @@ ShardMatrix generateShardMatrix() } +void assertShard(Shard shard, int index) { + string expectedPrefix = index == -1 ? "" : "legacyPackages"; + string expectedPostfix = index == -1 ? "" : ("shards." ~ index.to!string); + assert(shard.prefix == expectedPrefix, "Expected shard %s to have prefix '%s', but got %s".format(index, expectedPrefix, shard.prefix)); + assert(shard.postfix == expectedPostfix, "Expected shard %s to have postfix '%s', but got %s".format(index, expectedPostfix, shard.postfix)); + assert(shard.digit == index, "Expected shard %s to have digit %s, but got %s".format(index, index, shard.digit)); +} + @("generateShardMatrix") unittest { auto shards = generateShardMatrix(); //this repo doesn't include shards, so we should get the error message - assert(shards.include.length == 1); - assert(shards.include[0].prefix == ""); - assert(shards.include[0].postfix == ""); - assert(shards.include[0].digit == -1); + assert(shards.include.length == 1, "generateShardMatrix should return 1 shard, but got %s".format(shards.include.length)); + assertShard(shards.include[0], -1); } @@ -92,16 +101,9 @@ ShardMatrix splitToShards(int shardCount) unittest { auto shards = splitToShards(3); - assert(shards.include.length == 3); - assert(shards.include[0].prefix == "legacyPackages"); - assert(shards.include[0].postfix == "shards.0"); - assert(shards.include[0].digit == 0); - assert(shards.include[1].prefix == "legacyPackages"); - assert(shards.include[1].postfix == "shards.1"); - assert(shards.include[1].digit == 1); - assert(shards.include[2].prefix == "legacyPackages"); - assert(shards.include[2].postfix == "shards.2"); - assert(shards.include[2].digit == 2); + assert(shards.include.length == 3, "Expectes splitToShards(3) to return 3 shards, but got %s".format(shards.include.length)); + foreach(i, shard; shards.include.parallel) + assertShard(shard, i.to!int); } diff --git a/packages/mcl/src/src/mcl/utils/array.d b/packages/mcl/src/src/mcl/utils/array.d index ed7bc833..8d69ab73 100644 --- a/packages/mcl/src/src/mcl/utils/array.d +++ b/packages/mcl/src/src/mcl/utils/array.d @@ -19,10 +19,14 @@ T[] uniqIfSame(T)(T[] arr) @("uniqIfSame") unittest { - assert(uniqIfSame([1, 1, 1, 1]) == [1]); - assert(uniqIfSame([1, 2, 3, 4]) == [1, 2, 3, 4]); - assert(uniqIfSame(["a", "a", "a", "a"]) == ["a"]); - assert(uniqIfSame(["a", "b", "c", "d"]) == ["a", "b", "c", "d"]); + assert(uniqIfSame([1, 1, 1, 1]) == [1], + "uniqIfSame should return [1] for [1, 1, 1, 1], but got " ~ uniqIfSame([1, 1, 1, 1]).to!string); + assert(uniqIfSame([1, 2, 3, 4]) == [1, 2, 3, 4], + "uniqIfSame should return [1, 2, 3, 4] for [1, 2, 3, 4], but got " ~ uniqIfSame([1, 2, 3, 4]).to!string); + assert(uniqIfSame(["a", "a", "a", "a"]) == ["a"], + "uniqIfSame should return [\"a\"] for [\"a\", \"a\", \"a\", \"a\"], but got " ~ uniqIfSame(["a", "a", "a", "a"]).to!string); + assert(uniqIfSame(["a", "b", "c", "d"]) == ["a", "b", "c", "d"], + "uniqIfSame should return [\"a\", \"b\", \"c\", \"d\"] for [\"a\", \"b\", \"c\", \"d\"], but got " ~ uniqIfSame(["a", "b", "c", "d"]).to!string); } T uniqArrays(T)(T s) @@ -39,16 +43,17 @@ T uniqArrays(T)(T s) @("uniqArrays") unittest { - assert(uniqArrays([1, 2, 3, 4, 1, 2, 3, 4]) == [1, 2, 3, 4]); - assert(uniqArrays("aabbccdd") == "aabbccdd"); - assert(uniqArrays(5) == 5); + assert(uniqArrays([1, 2, 3, 4, 1, 2, 3, 4]) == [1, 2, 3, 4], + "uniqArrays should return [1, 2, 3, 4] for [1, 2, 3, 4, 1, 2, 3, 4], but got " ~ uniqArrays([1, 2, 3, 4, 1, 2, 3, 4]).to!string); + assert(uniqArrays("aabbccdd") == "aabbccdd", + "uniqArrays should return \"aabbccdd\" for \"aabbccdd\", but got " ~ uniqArrays("aabbccdd").to!string); + assert(uniqArrays(5) == 5, "uniqArrays should return 5 for 5, but got " ~ uniqArrays(5).to!string); struct TestStruct { int[] a; string b; } - assert(uniqArrays(TestStruct([1, 2, 3, 4, 1, 2, 3, 4], "aabbccdd")) == TestStruct([ - 1, 2, 3, 4 - ], "aabbccdd")); + assert(uniqArrays(TestStruct([1, 2, 3, 4, 1, 2, 3, 4], "aabbccdd")) == TestStruct([1, 2, 3, 4], "aabbccdd"), + "uniqArrays should return TestStruct([1, 2, 3, 4], \"aabbccdd\") for TestStruct([1, 2, 3, 4, 1, 2, 3, 4], \"aabbccdd\"), but got " ~ uniqArrays(TestStruct([1, 2, 3, 4, 1, 2, 3, 4], "aabbccdd")).to!string); } diff --git a/packages/mcl/src/src/mcl/utils/cachix.d b/packages/mcl/src/src/mcl/utils/cachix.d index 3345ebfe..9662ad15 100644 --- a/packages/mcl/src/src/mcl/utils/cachix.d +++ b/packages/mcl/src/src/mcl/utils/cachix.d @@ -12,7 +12,9 @@ in (workspace && machine && deploymentId) => unittest { assert(getCachixDeploymentApiUrl("my-workspace", "my-machine", 123) == - "https://app.cachix.org/api/v1/deploy/deployment/my-workspace/my-machine/123"); + "https://app.cachix.org/api/v1/deploy/deployment/my-workspace/my-machine/123", + "getCachixDeploymentApiUrl(\"my-workspace\", \"my-machine\", 123) should return \"https://app.cachix.org/api/v1/deploy/deployment/my-workspace/my-machine/123\", but returned %s" + .fmt(getCachixDeploymentApiUrl("my-workspace", "my-machine", 123))); } diff --git a/packages/mcl/src/src/mcl/utils/coda.d b/packages/mcl/src/src/mcl/utils/coda.d index 63175c0e..c459fb94 100644 --- a/packages/mcl/src/src/mcl/utils/coda.d +++ b/packages/mcl/src/src/mcl/utils/coda.d @@ -112,7 +112,7 @@ struct CodaApiClient auto apiToken = environment.get("CODA_API_TOKEN"); auto coda = CodaApiClient(apiToken); auto resp = coda.getDocument("6vM0kjfQP6"); - assert(resp.id == "6vM0kjfQP6"); + assert(resp.id == "6vM0kjfQP6", "Expected document ID to be 6vM0kjfQP6, but got %s".format(resp.id)); } Document[] listDocuments() @@ -127,7 +127,7 @@ struct CodaApiClient auto apiToken = environment.get("CODA_API_TOKEN"); auto coda = CodaApiClient(apiToken); auto resp = coda.listDocuments(); - assert(resp.length > 0); + assert(resp.length > 0, "Expected at least one document, but got 0"); } struct InitialPage @@ -189,9 +189,9 @@ struct CodaApiClient InitialPage.PageContent("canvas", InitialPage.PageContent.CanvasContent("html", "

This is rich text

"))); auto resp = coda.createDocument("Test Document", "", "Europe/Sofia", "", initialPage); - assert(resp.name == "Test Document"); + assert(resp.name == "Test Document", "Expected document name to be \"Test Document\", but got %s".format(resp.name)); coda.deleteDocument(resp.id); - assertThrown!(HTTPStatusException)(coda.getDocument(resp.id)); + assertThrown!(HTTPStatusException)(coda.getDocument(resp.id), "Expected coda.getDocument to throw an exception, but it didn't"); } Document patchDocument(string documentId, string title = "", string iconName = "") @@ -217,7 +217,7 @@ struct CodaApiClient auto resp = coda.createDocument("Test Document", "", "Europe/Sofia", "", initialPage); coda.patchDocument(resp.id, "Patched Document", ""); auto patched = coda.getDocument(resp.id); - assert(patched.name == "Patched Document"); + assert(patched.name == "Patched Document", "Expected document name to be \"Patched Document\", but got %s".format(patched.name)); coda.deleteDocument(patched.id); } @@ -301,7 +301,7 @@ struct CodaApiClient auto apiToken = environment.get("CODA_API_TOKEN"); auto coda = CodaApiClient(apiToken); auto resp = coda.listTables("6vM0kjfQP6"); - assert(resp.length > 0); + assert(resp.length > 0, "Expected at least one table, but got 0"); } Table getTable(string documentId, string tableId) @@ -317,7 +317,7 @@ struct CodaApiClient auto coda = CodaApiClient(apiToken); auto tables = coda.listTables("6vM0kjfQP6"); auto resp = coda.getTable("6vM0kjfQP6", tables[0].id); - assert(resp.id == tables[0].id); + assert(resp.id == tables[0].id, "Expected table ID to be %s, but got %s".format(tables[0].id, resp.id)); } struct Column @@ -376,7 +376,7 @@ struct CodaApiClient auto coda = CodaApiClient(apiToken); auto tables = coda.listTables("6vM0kjfQP6"); auto resp = coda.listColumns("6vM0kjfQP6", tables[0].id); - assert(resp.length > 0); + assert(resp.length > 0, "Expected at least one column, but got 0"); } Column getColumn(string documentId, string tableId, string columnId) @@ -393,7 +393,7 @@ struct CodaApiClient auto tables = coda.listTables("6vM0kjfQP6"); auto columns = coda.listColumns("6vM0kjfQP6", tables[0].id); auto resp = coda.getColumn("6vM0kjfQP6", tables[0].id, columns[0].id); - assert(resp.id == columns[0].id); + assert(resp.id == columns[0].id, "Expected column ID to be %s, but got %s".format(columns[0].id, resp.id)); } alias RowValue = SumType!(string, int, bool, string[], int[], bool[]); @@ -443,7 +443,7 @@ struct CodaApiClient auto coda = CodaApiClient(apiToken); auto tables = coda.listTables("6vM0kjfQP6"); auto resp = coda.listRows("6vM0kjfQP6", tables[0].id); - assert(resp.length > 0); + assert(resp.length > 0, "Expected at least one row, but got 0"); } struct InsertRowsReturn @@ -496,9 +496,9 @@ struct CodaApiClient ]) ]; auto resp = coda.insertRows("dEJJPwdxcw", tables[0].id, rows); - assert(resp.length > 0); + assert(resp.length > 0, "Expected at least one row after inserting rows, but got 0"); coda.deleteRow("dEJJPwdxcw", tables[0].id, resp[0]); - assertThrown!(HTTPStatusException)(coda.getRow("dEJJPwdxcw", tables[0].id, resp[0])); + assertThrown!(HTTPStatusException)(coda.getRow("dEJJPwdxcw", tables[0].id, resp[0]), "Expected coda.getRow to throw an exception, but it didn't"); } Row getRow(string documentId, string tableId, string rowId) @@ -515,7 +515,7 @@ struct CodaApiClient auto tables = coda.listTables("dEJJPwdxcw"); auto rows = coda.listRows("dEJJPwdxcw", tables[0].id); auto resp = coda.getRow("dEJJPwdxcw", tables[0].id, rows[0].id); - assert(resp.id == rows[0].id); + assert(resp.id == rows[0].id, "Expected row ID to be %s, but got %s".format(rows[0].id, resp.id)); } struct UpdateRowReturn @@ -552,7 +552,7 @@ struct CodaApiClient auto updated = coda.updateRow("dEJJPwdxcw", tables[0].id, resp[0], newRow); - assert(updated == resp[0]); + assert(updated == resp[0], "Expected updated row ID to be %s, but got %s".format(resp[0], updated)); coda.deleteRow("dEJJPwdxcw", tables[0].id, resp[0]); } @@ -583,8 +583,8 @@ struct CodaApiClient auto buttonColumn = "c-9MA3HmNByK"; auto rowId = "i-HV8Hsf2O8H"; auto buttonResp = coda.pushButton("dEJJPwdxcw", tables[0].id, rowId, buttonColumn); - assert(buttonResp.rowId == rowId); - assert(buttonResp.columnId == buttonColumn); + assert(buttonResp.rowId == rowId, "Expected row ID to be %s, but got %s".format(rowId, buttonResp.rowId)); + assert(buttonResp.columnId == buttonColumn, "Expected column ID to be %s, but got %s".format(buttonColumn, buttonResp.columnId)); } struct Category @@ -604,7 +604,7 @@ struct CodaApiClient auto apiToken = environment.get("CODA_API_TOKEN"); auto coda = CodaApiClient(apiToken); auto resp = coda.listCategories(); - assert(resp.length > 0); + assert(resp.length > 0, "Expected at least one category, but got 0"); } void triggerAutomation(string documentId, string automationId, JSONValue req) diff --git a/packages/mcl/src/src/mcl/utils/env.d b/packages/mcl/src/src/mcl/utils/env.d index d7a9e1e8..1b379ca4 100644 --- a/packages/mcl/src/src/mcl/utils/env.d +++ b/packages/mcl/src/src/mcl/utils/env.d @@ -72,6 +72,7 @@ unittest { import std.process : environment; import std.exception : assertThrown; + import std.conv : to; environment["A"] = "1"; environment["B"] = "2"; @@ -79,14 +80,14 @@ unittest auto config = parseEnv!Config; - assert(config.a == 1); - assert(config.b == "2"); - assert(config.c == 1.0); - assert(config.opt is null); + assert(config.a == 1, "config.a should be 1, but got " ~ config.a.to!string); + assert(config.b == "2", "config.b should be \"2\", but got " ~ config.b); + assert(config.c == 1.0, "config.c should be 1.0, but got " ~ config.c.to!string); + assert(config.opt is null, "config.opt should be null, but got " ~ config.opt); environment["OPT"] = "3"; config = parseEnv!Config; - assert(config.opt == "3"); + assert(config.opt == "3", "config.opt should be \"3\", but got " ~ config.opt); environment.remove("A"); assertThrown(config = parseEnv!Config, "missing environment variables:\nA\n"); diff --git a/packages/mcl/src/src/mcl/utils/fetch.d b/packages/mcl/src/src/mcl/utils/fetch.d index 99226b14..b1697a56 100644 --- a/packages/mcl/src/src/mcl/utils/fetch.d +++ b/packages/mcl/src/src/mcl/utils/fetch.d @@ -2,6 +2,7 @@ module mcl.utils.fetch; import mcl.utils.test; import std.json : JSONValue; +import std.format : fmt = format; JSONValue fetchJson(string url, string authToken = "") { @@ -25,7 +26,12 @@ JSONValue fetchJson(string url, string authToken = "") unittest { auto json = fetchJson("https://v2.jokeapi.dev/joke/Programming?type=single&idRange=40"); - assert(json["category"].str == "Programming"); - assert(json["type"].str == "single"); - assert(json["joke"].str == "Debugging: Removing the needles from the haystack."); + string actualCategory = json["category"].str; + assert(actualCategory == "Programming", "Expected category to be 'Programming', but got '%s'".fmt(actualCategory)); + + string actualType = json["type"].str; + assert(actualType == "single", "Expected type to be 'single', but got '%s'".fmt(actualType)); + + string actualJoke = json["joke"].str; + assert(actualJoke == "Debugging: Removing the needles from the haystack.", "Expected joke to be 'Debugging: Removing the needles from the haystack.', but got '%s'".fmt(actualJoke)); } diff --git a/packages/mcl/src/src/mcl/utils/json.d b/packages/mcl/src/src/mcl/utils/json.d index 178eb82d..117c1fd8 100644 --- a/packages/mcl/src/src/mcl/utils/json.d +++ b/packages/mcl/src/src/mcl/utils/json.d @@ -11,6 +11,7 @@ import std.algorithm : map; import std.array : join, array, replace, split; import std.datetime : SysTime; import std.sumtype : SumType, isSumType; +import std.format : format; import core.stdc.string : strlen; string getStrOrDefault(JSONValue value, string defaultValue = "") @@ -193,40 +194,50 @@ version (unittest) @("toJSON") unittest { - import std.stdio : writeln; - - assert(1.toJSON == JSONValue(1)); - assert(true.toJSON == JSONValue(true)); - assert("test".toJSON == JSONValue("test")); - assert([1, 2, 3].toJSON == JSONValue([1, 2, 3])); - assert(["a", "b", "c"].toJSON == JSONValue(["a", "b", "c"])); - assert([TestEnum.a, TestEnum.b, TestEnum.c].toJSON == JSONValue( - ["supercalifragilisticexpialidocious", "b", "c"])); + void assertEqual(JSONValue actual, JSONValue expected) { + assert(actual == expected, format("Expected %s, but got %s", expected, actual)); + } + + assertEqual(1.toJSON, JSONValue(1)); + assertEqual(true.toJSON, JSONValue(true)); + assertEqual("test".toJSON, JSONValue("test")); + assertEqual([1, 2, 3].toJSON, JSONValue([1, 2, 3])); + assertEqual(["a", "b", "c"].toJSON, JSONValue(["a", "b", "c"])); + assertEqual([TestEnum.a, TestEnum.b, TestEnum.c].toJSON, JSONValue(["supercalifragilisticexpialidocious", "b", "c"])); + TestStruct testStruct = {1, "test", true}; - assert(testStruct.toJSON == JSONValue([ + auto actual = testStruct.toJSON; + auto expected = JSONValue([ + "a": JSONValue(1), + "b": JSONValue("test"), + "c": JSONValue(true) + ]); + assertEqual(actual, expected); + + TestStruct2 testStruct2 = {1, testStruct}; + actual = testStruct2.toJSON; + expected = JSONValue([ + "a": JSONValue(1), + "b": JSONValue([ "a": JSONValue(1), "b": JSONValue("test"), "c": JSONValue(true) - ])); - TestStruct2 testStruct2 = {1, testStruct}; - assert(testStruct2.toJSON == JSONValue([ + ]) + ]); + assertEqual(actual, expected); + + TestStruct3 testStruct3 = {1, testStruct2}; + actual = testStruct3.toJSON; + expected = JSONValue([ + "a": JSONValue(1), + "b": JSONValue([ "a": JSONValue(1), "b": JSONValue([ "a": JSONValue(1), "b": JSONValue("test"), "c": JSONValue(true) ]) - ])); - TestStruct3 testStruct3 = {1, testStruct2}; - assert(testStruct3.toJSON == JSONValue([ - "a": JSONValue(1), - "b": JSONValue([ - "a": JSONValue(1), - "b": JSONValue([ - "a": JSONValue(1), - "b": JSONValue("test"), - "c": JSONValue(true) - ]) - ]) - ])); + ]) + ]); + assertEqual(actual, expected); } diff --git a/packages/mcl/src/src/mcl/utils/nix.d b/packages/mcl/src/src/mcl/utils/nix.d index cc62af75..3222bab3 100644 --- a/packages/mcl/src/src/mcl/utils/nix.d +++ b/packages/mcl/src/src/mcl/utils/nix.d @@ -233,26 +233,32 @@ import std.ascii : isUpper; return res.replace(";;", ";"); } + + @("toNix") unittest { + void assertToNix(T)(T input, string expected) { + auto actual = toNix(input); + assert(actual == expected, fmt("For input '%s', expected '%s', but got '%s'", input, expected, actual)); + } struct TestStruct { int a; string b; bool c; } - assert(toNix(TestStruct(1, "hello", true)) == "{\n\ta = 1;\n\tb = \"hello\";\n\tc = true;\n}"); - assert(toNix(true) == "true"); - assert(toNix("hello") == "\"hello\""); - assert(toNix(1) == "1"); + assertToNix(TestStruct(1, "hello", true), "{\n\ta = 1;\n\tb = \"hello\";\n\tc = true;\n}"); + assertToNix(true, "true"); + assertToNix("hello", "\"hello\""); + assertToNix(1, "1"); struct TestStruct2 { int a; TestStruct b; } - assert(toNix(TestStruct2(1, TestStruct(2, "hello", false))) == "{\n\ta = 1;\n\tb = {\n\t\ta = 2;\n\t\tb = \"hello\";\n\t\tc = false;\n\t};\n}"); + assertToNix(TestStruct2(1, TestStruct(2, "hello", false)), "{\n\ta = 1;\n\tb = {\n\t\ta = 2;\n\t\tb = \"hello\";\n\t\tc = false;\n\t};\n}"); } @("nix.run") @@ -266,7 +272,7 @@ unittest auto p = __FILE__.absolutePath.dirName; string output = nix().run(p ~ "/test/test.nix", [ "--file"]); - assert(output == "Hello World"); + assert(output == "Hello World", "Expected 'Hello World', but got '" ~ output ~ "'"); } @("nix.build!JSONValue") @@ -280,7 +286,9 @@ unittest auto p = __FILE__.absolutePath.dirName; JSONValue output = nix().build!JSONValue(p ~ "/test/test.nix", ["--file"]).array.front; - assert(execute([output["outputs"]["out"].str ~ "/bin/helloWorld"]).strip == "Hello World"); + auto actual = execute([output["outputs"]["out"].str ~ "/bin/helloWorld"]).strip; + auto expected = "Hello World"; + assert(actual == expected, fmt("Expected %s, but got %s", expected, actual)); } @("nix.eval!JSONValue") @@ -294,5 +302,5 @@ unittest auto expectedOutputFile = inputFile.setExtension("json"); auto output = nix().eval!JSONValue(inputFile, ["--file"]); - assert(output == expectedOutputFile.readText.parseJSON); + assert(output == expectedOutputFile.readText.parseJSON, "Expected " ~ expectedOutputFile.readText ~ ", but got " ~ output.toString()); } diff --git a/packages/mcl/src/src/mcl/utils/path.d b/packages/mcl/src/src/mcl/utils/path.d index 6654aeb2..3f47b9b8 100644 --- a/packages/mcl/src/src/mcl/utils/path.d +++ b/packages/mcl/src/src/mcl/utils/path.d @@ -4,6 +4,7 @@ import mcl.utils.test; import std.process : execute; import std.string : strip; import std.file : mkdirRecurse, rmdir, exists; +import std.format : format; string getTopLevel() { @@ -19,7 +20,7 @@ string rootDir() => _rootDir == "" ? _rootDir = getTopLevel() : _rootDir; @("rootDir") unittest { - assert(rootDir() == getTopLevel()); + assert(rootDir() == getTopLevel(), "Expected rootDir to return %s, got %s".format(getTopLevel(), rootDir())); } string _resultDir = ""; @@ -28,7 +29,7 @@ string resultDir() => _resultDir == "" ? _resultDir = rootDir() ~ ".result/" : _ @("resultDir") unittest { - assert(resultDir() == rootDir() ~ ".result/"); + assert(resultDir() == rootDir() ~ ".result/", "Expected resultDir to return %s, got %s".format(rootDir() ~ ".result/", resultDir())); } string _gcRootsDir = ""; @@ -37,7 +38,7 @@ string gcRootsDir() => _gcRootsDir == "" ? _gcRootsDir = resultDir() ~ "gc-roots @("gcRootsDir") unittest { - assert(gcRootsDir() == resultDir() ~ "gc-roots/"); + assert(gcRootsDir() == resultDir() ~ "gc-roots/", "Expected gcRootsDir to return %s, got %s".format(resultDir() ~ "gc-roots/", gcRootsDir())); } void createResultDirs() => mkdirRecurse(gcRootsDir); @@ -46,9 +47,9 @@ void createResultDirs() => mkdirRecurse(gcRootsDir); unittest { createResultDirs(); - assert(gcRootsDir.exists); + assert(gcRootsDir.exists, "Expected gcRootsDir to exist, but it doesn't"); // rmdir(gcRootsDir()); // rmdir(resultDir()); - // assert(!gcRootsDir.exists); + // assert(!gcRootsDir.exists, "Expected gcRootsDir to not exist, but it does"); } diff --git a/packages/mcl/src/src/mcl/utils/process.d b/packages/mcl/src/src/mcl/utils/process.d index 76ce1974..4f6b9fe6 100644 --- a/packages/mcl/src/src/mcl/utils/process.d +++ b/packages/mcl/src/src/mcl/utils/process.d @@ -4,6 +4,7 @@ import std.process : ProcessPipes; import std.string : split, strip; import core.sys.posix.unistd : geteuid; import std.json : JSONValue, parseJSON; +import std.format : format; bool isRoot() => geteuid() == 0; @@ -57,8 +58,11 @@ unittest { import std.exception : assertThrown; - assert(execute(["echo", "hello"]) == "hello"); - assert(execute(["true"]) == ""); + auto actual = execute(["echo", "hello"]); + assert(actual == "hello", format("Expected '%s', but got '%s'", "hello", actual)); + + actual = execute(["true"]); + assert(actual == "", format("Expected '%s', but got '%s'", "", actual)); // assertThrown(execute(["false"]), "Command `false` failed with status 1"); } diff --git a/packages/mcl/src/src/mcl/utils/string.d b/packages/mcl/src/src/mcl/utils/string.d index 31fb50df..632a66d2 100644 --- a/packages/mcl/src/src/mcl/utils/string.d +++ b/packages/mcl/src/src/mcl/utils/string.d @@ -3,6 +3,7 @@ module mcl.utils.string; import mcl.utils.test; import std.conv : to; import std.exception : assertThrown; +import std.format : format; string lowerCaseFirst(string r) { @@ -30,22 +31,25 @@ string camelCaseToCapitalCase(string camelCase) @("camelCaseToCapitalCase") unittest { - assert(camelCaseToCapitalCase("camelCase") == "CAMEL_CASE"); - - assert(camelCaseToCapitalCase("") == ""); - assert(camelCaseToCapitalCase("_") == "_"); - assert(camelCaseToCapitalCase("a") == "A"); - assert(camelCaseToCapitalCase("ab") == "AB"); - assert(camelCaseToCapitalCase("aB") == "A_B"); - assert(camelCaseToCapitalCase("aBc") == "A_BC"); - assert(camelCaseToCapitalCase("aBC") == "A_BC"); - assert(camelCaseToCapitalCase("aBCD") == "A_BCD"); - assert(camelCaseToCapitalCase("aBcD") == "A_BC_D"); - - assert(camelCaseToCapitalCase("rpcUrl") == "RPC_URL"); - assert(camelCaseToCapitalCase("parsedJSON") == "PARSED_JSON"); - assert(camelCaseToCapitalCase("fromXmlToJson") == "FROM_XML_TO_JSON"); - assert(camelCaseToCapitalCase("fromXML2JSON") == "FROM_XML2JSON"); + void assertCCToCC(string input, string expected) { + auto actual = camelCaseToCapitalCase(input); + assert(actual == expected, format("For input '%s', expected '%s', but got '%s'", input, expected, actual)); + } + + assertCCToCC("camelCase", "CAMEL_CASE"); + assertCCToCC("", ""); + assertCCToCC("_", "_"); + assertCCToCC("a", "A"); + assertCCToCC("ab", "AB"); + assertCCToCC("aB", "A_B"); + assertCCToCC("aBc", "A_BC"); + assertCCToCC("aBC", "A_BC"); + assertCCToCC("aBCD", "A_BCD"); + assertCCToCC("aBcD", "A_BC_D"); + assertCCToCC("rpcUrl", "RPC_URL"); + assertCCToCC("parsedJSON", "PARSED_JSON"); + assertCCToCC("fromXmlToJson", "FROM_XML_TO_JSON"); + assertCCToCC("fromXML2JSON", "FROM_XML2JSON"); } string kebabCaseToCamelCase(string kebabCase) @@ -65,23 +69,30 @@ string kebabCaseToCamelCase(string kebabCase) @("kebabCaseToCamelCase") unittest { - assert(kebabCaseToCamelCase("kebab-case") == "kebabCase"); - assert(kebabCaseToCamelCase("kebab-case-") == "kebabCase"); - assert(kebabCaseToCamelCase("kebab-case--") == "kebabCase"); - assert(kebabCaseToCamelCase("kebab-case--a") == "kebabCaseA"); - assert(kebabCaseToCamelCase("kebab-case--a-") == "kebabCaseA"); - - assert(kebabCaseToCamelCase( - "once-upon-a-midnight-dreary-while-i-pondered-weak-and-weary" ~ - "-over-many-a-quaint-and-curious-volume-of-forgotten-lore" ~ - "-while-i-nodded-nearly-napping-suddenly-there-came-a-tapping" ~ - "-as-of-someone-gently-rapping-rapping-at-my-chamber-door" ~ - "-tis-some-visitor-i-muttered-tapping-at-my-chamber-door" ~ - "-only-this-and-nothing-more") == "onceUponAMidnightDrearyWhileIPonderedWeakAndWeary" ~ - "OverManyAQuaintAndCuriousVolumeOfForgottenLore" ~ "WhileINoddedNearlyNappingSuddenlyThereCameATapping" ~ - "AsOfSomeoneGentlyRappingRappingAtMyChamberDoor" ~ - "TisSomeVisitorIMutteredTappingAtMyChamberDoor" ~ - "OnlyThisAndNothingMore"); + void assertKCToCC(string input, string expected) { + auto actual = kebabCaseToCamelCase(input); + assert(actual == expected, format("For input '%s', expected '%s', but got '%s'", input, expected, actual)); + } + + assertKCToCC("kebab-case", "kebabCase"); + assertKCToCC("kebab-case-", "kebabCase"); + assertKCToCC("kebab-case--", "kebabCase"); + assertKCToCC("kebab-case--a", "kebabCaseA"); + assertKCToCC("kebab-case--a-", "kebabCaseA"); + + assertKCToCC( + "once-upon-a-midnight-dreary-while-i-pondered-weak-and-weary" ~ + "-over-many-a-quaint-and-curious-volume-of-forgotten-lore" ~ + "-while-i-nodded-nearly-napping-suddenly-there-came-a-tapping" ~ + "-as-of-someone-gently-rapping-rapping-at-my-chamber-door" ~ + "-tis-some-visitor-i-muttered-tapping-at-my-chamber-door" ~ + "-only-this-and-nothing-more", + "onceUponAMidnightDrearyWhileIPonderedWeakAndWeary" ~ + "OverManyAQuaintAndCuriousVolumeOfForgottenLore" ~ "WhileINoddedNearlyNappingSuddenlyThereCameATapping" ~ + "AsOfSomeoneGentlyRappingRappingAtMyChamberDoor" ~ + "TisSomeVisitorIMutteredTappingAtMyChamberDoor" ~ + "OnlyThisAndNothingMore" + ); } @@ -118,6 +129,11 @@ string enumToString(E)(in E value) if (is(E == enum)) @("enumToString") unittest { + void assertEnumToString(T)(T input, string expected) if (is(T == enum)) { + auto actual = enumToString(input); + assert(actual == expected, format("For input '%s', expected '%s', but got '%s'", input, expected, actual)); + } + enum TestEnum { a1, @@ -125,9 +141,9 @@ unittest c3 } - assert(enumToString(TestEnum.a1) == "a1"); - assert(enumToString(TestEnum.b2) == "b2"); - assert(enumToString(TestEnum.c3) == "c3"); + assertEnumToString(TestEnum.a1, "a1"); + assertEnumToString(TestEnum.b2, "b2"); + assertEnumToString(TestEnum.c3, "c3"); enum TestEnumWithRepr { @@ -136,9 +152,9 @@ unittest @StringRepresentation("field-3") c } - assert(enumToString(TestEnumWithRepr.a) == "field1"); - assert(enumToString(TestEnumWithRepr.b) == "field_2"); - assert(enumToString(TestEnumWithRepr.c) == "field-3"); + assertEnumToString(TestEnumWithRepr.a, "field1"); + assertEnumToString(TestEnumWithRepr.b, "field_2"); + assertEnumToString(TestEnumWithRepr.c, "field-3"); } enum size_t getMaxEnumMemberNameLength(E) = () @@ -165,7 +181,7 @@ unittest @StringRepresentation("c123") c } - static assert(getMaxEnumMemberNameLength!EnumLen == 4); + static assert(getMaxEnumMemberNameLength!EnumLen == 4, "getMaxEnumMemberNameLength should return 4 for EnumLen"); } struct MaxWidth