diff options
Diffstat (limited to 'testament/lib')
-rw-r--r-- | testament/lib/readme.md | 4 | ||||
-rw-r--r-- | testament/lib/stdtest/netutils.nim | 13 | ||||
-rw-r--r-- | testament/lib/stdtest/specialpaths.nim | 55 | ||||
-rw-r--r-- | testament/lib/stdtest/testutils.nim | 126 | ||||
-rw-r--r-- | testament/lib/stdtest/unittest_light.nim | 37 |
5 files changed, 235 insertions, 0 deletions
diff --git a/testament/lib/readme.md b/testament/lib/readme.md new file mode 100644 index 000000000..20e866338 --- /dev/null +++ b/testament/lib/readme.md @@ -0,0 +1,4 @@ +This directory contains helper files used by several tests, to avoid +code duplication, akin to a std extension tailored for testament. + +Some of these could later migrate to stdlib. diff --git a/testament/lib/stdtest/netutils.nim b/testament/lib/stdtest/netutils.nim new file mode 100644 index 000000000..5115390e0 --- /dev/null +++ b/testament/lib/stdtest/netutils.nim @@ -0,0 +1,13 @@ +import std/[nativesockets, asyncdispatch, os] + +proc bindAvailablePort*(handle: SocketHandle, port = Port(0)): Port = + ## See also `asynchttpserver.getPort`. + block: + var name: Sockaddr_in + name.sin_family = typeof(name.sin_family)(toInt(AF_INET)) + name.sin_port = htons(uint16(port)) + name.sin_addr.s_addr = htonl(INADDR_ANY) + if bindAddr(handle, cast[ptr SockAddr](addr(name)), + sizeof(name).Socklen) < 0'i32: + raiseOSError(osLastError(), $port) + result = getLocalAddr(handle, AF_INET)[1] diff --git a/testament/lib/stdtest/specialpaths.nim b/testament/lib/stdtest/specialpaths.nim new file mode 100644 index 000000000..e214d113d --- /dev/null +++ b/testament/lib/stdtest/specialpaths.nim @@ -0,0 +1,55 @@ +#[ +todo: move findNimStdLibCompileTime, findNimStdLib here +xxx: factor pending https://github.com/timotheecour/Nim/issues/616 + +## note: $lib vs $nim +note: these can resolve to 3 different paths if running via `nim c --lib:lib foo`, +eg if compiler was installed via nimble (or is in nim path), and nim is external +(ie not in `$lib/../bin/` dir) + +import "$lib/../compiler/nimpaths" # <- most robust if you want to favor --lib:lib +import "$nim/compiler/nimpaths" +import compiler/nimpaths +]# + +import os +when defined(nimPreviewSlimSystem): + import std/assertions + +# Note: all the const paths defined here are known at compile time and valid +# so long Nim repo isn't relocated after compilation. +# This means the binaries they produce will embed hardcoded paths, which +# isn't appropriate for some applications that need to be relocatable. + +const + sourcePath = currentSourcePath() + # robust way to derive other paths here + # We don't depend on PATH so this is robust to having multiple nim binaries + nimRootDir* = sourcePath.parentDir.parentDir.parentDir.parentDir ## root of Nim repo + testsFname* = "tests" + stdlibDir* = nimRootDir / "lib" + systemPath* = stdlibDir / "system.nim" + testsDir* = nimRootDir / testsFname + buildDir* = nimRootDir / "build" + ## refs #10268: all testament generated files should go here to avoid + ## polluting .gitignore + +proc splitTestFile*(file: string): tuple[cat: string, path: string] = + ## At least one directory is required in the path, to use as a category name + runnableExamples: + doAssert splitTestFile("tests/fakedir/tfakename.nim") == ("fakedir", "tests/fakedir/tfakename.nim".unixToNativePath) + for p in file.parentDirs(inclusive = false): + let parent = p.parentDir + if parent.lastPathPart == testsFname: + result.cat = p.lastPathPart + let dir = getCurrentDir() + if file.isRelativeTo(dir): + result.path = file.relativePath(dir) + else: + result.path = file + return result + raiseAssert "file must match this pattern: '/pathto/tests/dir/**/tfile.nim', got: '" & file & "'" + +static: + # sanity check + doAssert fileExists(systemPath) diff --git a/testament/lib/stdtest/testutils.nim b/testament/lib/stdtest/testutils.nim new file mode 100644 index 000000000..a490b17c8 --- /dev/null +++ b/testament/lib/stdtest/testutils.nim @@ -0,0 +1,126 @@ +import std/private/miscdollars +when defined(nimscript): + import std/os # xxx investigate why needed +else: + from std/os import getEnv +import std/[macros, genasts] + +template flakyAssert*(cond: untyped, msg = "", notifySuccess = true) = + ## API to deal with flaky or failing tests. This avoids disabling entire tests + ## altogether so that at least the parts that are working are kept being + ## tested. This also avoids making CI fail periodically for tests known to + ## be flaky. Finally, for known failures, passing `notifySuccess = true` will + ## log that the test succeeded, which may indicate that a bug was fixed + ## "by accident" and should be looked into. + const info = instantiationInfo(-1, true) + const expr = astToStr(cond) + if cond and not notifySuccess: + discard # silent success + else: + var msg2 = "" + toLocation(msg2, info.filename, info.line, info.column) + if cond: + # a flaky test is failing, we still report it but we don't fail CI + msg2.add " FLAKY_SUCCESS " + else: + # a previously failing test is now passing, a pre-existing bug might've been + # fixed by accidend + msg2.add " FLAKY_FAILURE " + msg2.add $expr & " " & msg + echo msg2 + +when not defined(js) and not defined(nimscript): + import std/strutils + + proc greedyOrderedSubsetLines*(lhs, rhs: string, allowPrefixMatch = false): bool = + ## Returns true if each stripped line in `lhs` appears in rhs, using a greedy matching. + # xxx improve error reporting by showing the last matched pair + iterator splitLinesClosure(): string {.closure.} = + for line in splitLines(rhs.strip): + yield line + template isMatch(lhsi, rhsi): bool = + if allowPrefixMatch: + startsWith(rhsi, lhsi) + else: + lhsi == rhsi + + var rhsIter = splitLinesClosure + var currentLine = strip(rhsIter()) + + for line in lhs.strip.splitLines: + let line = line.strip + if line.len != 0: + while not isMatch(line, currentLine): + currentLine = strip(rhsIter()) + if rhsIter.finished: + return false + + if rhsIter.finished: + return false + return true + +template enableRemoteNetworking*: bool = + ## Allows contolling whether to run some test at a statement-level granularity. + ## Using environment variables simplifies propagating this all the way across + ## process calls, e.g. `testament all` calls itself, which in turns invokes + ## a `nim` invocation (possibly via additional intermediate processes). + getEnv("NIM_TESTAMENT_REMOTE_NETWORKING") == "1" + +template disableSSLTesting*: bool = + ## TODO: workaround for GitHub Action gcc 14 matrix; remove this + ## matrix and the flag after Azure agent supports ubuntu 24.04 + getEnv("NIM_TESTAMENT_DISABLE_SSL") == "1" + +template whenRuntimeJs*(bodyIf, bodyElse) = + ##[ + Behaves as `when defined(js) and not nimvm` (which isn't legal yet). + pending improvements to `nimvm`, this sugar helps; use as follows: + + whenRuntimeJs: + doAssert defined(js) + when nimvm: doAssert false + else: discard + do: + discard + ]## + when nimvm: bodyElse + else: + when defined(js): bodyIf + else: bodyElse + +template whenVMorJs*(bodyIf, bodyElse) = + ## Behaves as: `when defined(js) or nimvm` + when nimvm: bodyIf + else: + when defined(js): bodyIf + else: bodyElse + +template accept*(a) = + doAssert compiles(a) + +template reject*(a) = + doAssert not compiles(a) + +template disableVm*(body) = + when nimvm: discard + else: body + +macro assertAll*(body) = + ## works in VM, unlike `check`, `require` + runnableExamples: + assertAll: + 1+1 == 2 + var a = @[1, 2] # statements work + a.len == 2 + # remove this once these support VM, pending #10129 (closed but not yet fixed) + result = newStmtList() + for a in body: + result.add genAst(a, a2 = a.repr, info = lineInfo(a)) do: + # D20210421T014713:here + # xxx pending https://github.com/nim-lang/Nim/issues/12030, + # `typeof` should introduce its own scope, so that this + # is sufficient: `typeof(a)` instead of `typeof(block: a)` + when typeof(block: a) is void: a + else: + if not a: + raise newException(AssertionDefect, info & " " & a2) diff --git a/testament/lib/stdtest/unittest_light.nim b/testament/lib/stdtest/unittest_light.nim new file mode 100644 index 000000000..4ab1d7543 --- /dev/null +++ b/testament/lib/stdtest/unittest_light.nim @@ -0,0 +1,37 @@ +import std/assertions + + +proc mismatch*[T](lhs: T, rhs: T): string = + ## Simplified version of `unittest.require` that satisfies a common use case, + ## while avoiding pulling too many dependencies. On failure, diagnostic + ## information is provided that in particular makes it easy to spot + ## whitespace mismatches and where the mismatch is. + proc replaceInvisible(s: string): string = + for a in s: + case a + of '\n': result.add "\\n\n" + else: result.add a + + proc quoted(s: string): string = result.addQuoted s + + result.add '\n' + result.add "lhs:{" & replaceInvisible( + $lhs) & "}\nrhs:{" & replaceInvisible($rhs) & "}\n" + when compiles(lhs.len): + if lhs.len != rhs.len: + result.add "lhs.len: " & $lhs.len & " rhs.len: " & $rhs.len & '\n' + when compiles(lhs[0]): + var i = 0 + while i < lhs.len and i < rhs.len: + if lhs[i] != rhs[i]: break + i.inc + result.add "first mismatch index: " & $i & '\n' + if i < lhs.len and i < rhs.len: + result.add "lhs[i]: {" & quoted($lhs[i]) & "}\nrhs[i]: {" & quoted( + $rhs[i]) & "}\n" + result.add "lhs[0..<i]:{" & replaceInvisible($lhs[ + 0..<i]) & '}' + +proc assertEquals*[T](lhs: T, rhs: T, msg = "") = + if lhs != rhs: + doAssert false, mismatch(lhs, rhs) & '\n' & msg |