From 7ac1fc81fd119dba3739e05a89ad63fcee405ac1 Mon Sep 17 00:00:00 2001 From: c-blake Date: Mon, 31 Dec 2018 08:52:51 -0500 Subject: Resolve things raised in https://github.com/nim-lang/Nim/issues/10081 ? (#10084) * Resolve things raised in https://github.com/nim-lang/Nim/issues/10081 ? CDF is a standard ident in all things related to random numbers/sampling, and full words "cumulativeDistributionFunction" would be silly long, in this case, IMO. We use lowercase `cdf` to make it not look like a type, remove all looping from `sample` letting callers do it. Besides just side-stepping any `sampleSize` name choice, callers may want to filter out samples anyway which this makes slightly simpler. Also add two variants of `cumsum`, value return and in-place update distinguished by the var-ness of the first argument. Add tests for `int` and `float` for both `cumsum` and the new `sample`. (The sample tests exercise the value return mode of `cumsum`.) Functionality pre-this-PR `sample(a, w)` is now the almost as simple `for i in 0.. `cumsummed` to honor NEP1 style. Re-instate `cumsum` as the in-place transformation. Test both in `tests/stdlib/tmath.nim` and use `cumsummed` in the example code for sample since that's a simpler example. * Fix requests from https://github.com/nim-lang/Nim/pull/10084 : example in lib/pure/math.nim and comment whitespace in lib/pure/random.nim --- tests/stdlib/tmath.nim | 57 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 9 deletions(-) (limited to 'tests/stdlib') diff --git a/tests/stdlib/tmath.nim b/tests/stdlib/tmath.nim index 7c1851e7a..544fa8dc0 100644 --- a/tests/stdlib/tmath.nim +++ b/tests/stdlib/tmath.nim @@ -4,6 +4,8 @@ discard """ [Suite] random float +[Suite] cumsum + [Suite] random sample [Suite] ^ @@ -74,29 +76,66 @@ suite "random float": var rand2:float = random(1000000.0) check rand1 != rand2 +suite "cumsum": + test "cumsum int seq return": + let counts = [ 1, 2, 3, 4 ] + check counts.cumsummed == [ 1, 3, 6, 10 ] + + test "cumsum float seq return": + let counts = [ 1.0, 2.0, 3.0, 4.0 ] + check counts.cumsummed == [ 1.0, 3.0, 6.0, 10.0 ] + + test "cumsum int in-place": + var counts = [ 1, 2, 3, 4 ] + counts.cumsum + check counts == [ 1, 3, 6, 10 ] + + test "cumsum float in-place": + var counts = [ 1.0, 2.0, 3.0, 4.0 ] + counts.cumsum + check counts == [ 1.0, 3.0, 6.0, 10.0 ] + suite "random sample": - test "non-uniform array sample": + test "non-uniform array sample unnormalized int CDF": let values = [ 10, 20, 30, 40, 50 ] # values - let weight = [ 4, 3, 2, 1, 0 ] # weights aka unnormalized probabilities - let weightSum = 10.0 # sum of weights + let counts = [ 4, 3, 2, 1, 0 ] # weights aka unnormalized probabilities var histo = initCountTable[int]() - for v in sample(values, weight, 5000): - histo.inc(v) - check histo.len == 4 # number of non-zero in `weight` + let cdf = counts.cumsummed # unnormalized CDF + for i in 0 ..< 5000: + histo.inc(sample(values, cdf)) + check histo.len == 4 # number of non-zero in `counts` # Any one bin is a binomial random var for n samples, each with prob p of # adding a count to k; E[k]=p*n, Var k=p*(1-p)*n, approximately Normal for # big n. So, P(abs(k - p*n)/sqrt(p*(1-p)*n))>3.0) =~ 0.0027, while # P(wholeTestFails) =~ 1 - P(binPasses)^4 =~ 1 - (1-0.0027)^4 =~ 0.01. - for i, w in weight: - if w == 0: + for i, c in counts: + if c == 0: check values[i] notin histo continue - let p = float(w) / float(weightSum) + let p = float(c) / float(cdf[^1]) let n = 5000.0 let expected = p * n let stdDev = sqrt(n * p * (1.0 - p)) check abs(float(histo[values[i]]) - expected) <= 3.0 * stdDev + test "non-uniform array sample normalized float CDF": + let values = [ 10, 20, 30, 40, 50 ] # values + let counts = [ 0.4, 0.3, 0.2, 0.1, 0 ] # probabilities + var histo = initCountTable[int]() + let cdf = counts.cumsummed # normalized CDF + for i in 0 ..< 5000: + histo.inc(sample(values, cdf)) + check histo.len == 4 # number of non-zero in ``counts`` + for i, c in counts: + if c == 0: + check values[i] notin histo + continue + let p = float(c) / float(cdf[^1]) + let n = 5000.0 + let expected = p * n + let stdDev = sqrt(n * p * (1.0 - p)) + # NOTE: like unnormalized int CDF test, P(wholeTestFails) =~ 0.01. + check abs(float(histo[values[i]]) - expected) <= 3.0 * stdDev suite "^": test "compiles for valid types": -- cgit 1.4.1-2-gfad0 From 2fa35126b09e3487fbb82328238e59b9a4dd6d4c Mon Sep 17 00:00:00 2001 From: Federico Ceratto Date: Sat, 5 Jan 2019 21:12:53 +0000 Subject: Fix getAddrInfo, add IPPROTO_ICMPV6 Closes #10198 --- lib/posix/posix_linux_amd64_consts.nim | 1 + lib/posix/posix_nintendoswitch_consts.nim | 1 + lib/posix/posix_other_consts.nim | 1 + lib/pure/nativesockets.nim | 7 ++++-- tests/stdlib/tgetaddrinfo.nim | 36 +++++++++++++++++++++++++++++++ 5 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 tests/stdlib/tgetaddrinfo.nim (limited to 'tests/stdlib') diff --git a/lib/posix/posix_linux_amd64_consts.nim b/lib/posix/posix_linux_amd64_consts.nim index c23005b1e..dfbfe7f64 100644 --- a/lib/posix/posix_linux_amd64_consts.nim +++ b/lib/posix/posix_linux_amd64_consts.nim @@ -295,6 +295,7 @@ const IF_NAMESIZE* = cint(16) const IPPROTO_IP* = cint(0) const IPPROTO_IPV6* = cint(41) const IPPROTO_ICMP* = cint(1) +const IPPROTO_ICMPV6* = cint(58) const IPPROTO_RAW* = cint(255) const IPPROTO_TCP* = cint(6) const IPPROTO_UDP* = cint(17) diff --git a/lib/posix/posix_nintendoswitch_consts.nim b/lib/posix/posix_nintendoswitch_consts.nim index f0c0dd717..1e782d92e 100644 --- a/lib/posix/posix_nintendoswitch_consts.nim +++ b/lib/posix/posix_nintendoswitch_consts.nim @@ -237,6 +237,7 @@ const IF_NAMESIZE* = cint(16) const IPPROTO_IP* = cint(0) const IPPROTO_IPV6* = cint(41) const IPPROTO_ICMP* = cint(1) +const IPPROTO_ICMPV6* = cint(58) const IPPROTO_RAW* = cint(255) const IPPROTO_TCP* = cint(6) const IPPROTO_UDP* = cint(17) diff --git a/lib/posix/posix_other_consts.nim b/lib/posix/posix_other_consts.nim index 2b4b70941..cd5199078 100644 --- a/lib/posix/posix_other_consts.nim +++ b/lib/posix/posix_other_consts.nim @@ -302,6 +302,7 @@ var IF_NAMESIZE* {.importc: "IF_NAMESIZE", header: "".}: cint var IPPROTO_IP* {.importc: "IPPROTO_IP", header: "".}: cint var IPPROTO_IPV6* {.importc: "IPPROTO_IPV6", header: "".}: cint var IPPROTO_ICMP* {.importc: "IPPROTO_ICMP", header: "".}: cint +var IPPROTO_ICMPV6* {.importc: "IPPROTO_ICMPV6", header: "".}: cint var IPPROTO_RAW* {.importc: "IPPROTO_RAW", header: "".}: cint var IPPROTO_TCP* {.importc: "IPPROTO_TCP", header: "".}: cint var IPPROTO_UDP* {.importc: "IPPROTO_UDP", header: "".}: cint diff --git a/lib/pure/nativesockets.nim b/lib/pure/nativesockets.nim index f98f9a444..96c377187 100644 --- a/lib/pure/nativesockets.nim +++ b/lib/pure/nativesockets.nim @@ -71,6 +71,7 @@ type IPPROTO_IPV6, ## Internet Protocol Version 6. Unsupported on Windows. IPPROTO_RAW, ## Raw IP Packets Protocol. Unsupported on Windows. IPPROTO_ICMP ## Control message protocol. Unsupported on Windows. + IPPROTO_ICMPV6 ## Control message protocol for IPv6. Unsupported on Windows. Servent* = object ## information about a service name*: string @@ -154,6 +155,7 @@ when not useWinVersion: of IPPROTO_IPV6: result = posix.IPPROTO_IPV6 of IPPROTO_RAW: result = posix.IPPROTO_RAW of IPPROTO_ICMP: result = posix.IPPROTO_ICMP + of IPPROTO_ICMPV6: result = posix.IPPROTO_ICMPV6 else: proc toInt(domain: Domain): cint = @@ -179,7 +181,7 @@ proc toSockType*(protocol: Protocol): SockType = SOCK_STREAM of IPPROTO_UDP: SOCK_DGRAM - of IPPROTO_IP, IPPROTO_IPV6, IPPROTO_RAW, IPPROTO_ICMP: + of IPPROTO_IP, IPPROTO_IPV6, IPPROTO_RAW, IPPROTO_ICMP, IPPROTO_ICMPV6: SOCK_RAW proc createNativeSocket*(domain: Domain = AF_INET, @@ -255,7 +257,8 @@ proc getAddrInfo*(address: string, port: Port, domain: Domain = AF_INET, when not defined(freebsd) and not defined(openbsd) and not defined(netbsd) and not defined(android) and not defined(haiku): if domain == AF_INET6: hints.ai_flags = AI_V4MAPPED - var gaiResult = getaddrinfo(address, $port, addr(hints), result) + let socket_port = if sockType == SOCK_RAW: "" else: $port + var gaiResult = getaddrinfo(address, socket_port, addr(hints), result) if gaiResult != 0'i32: when useWinVersion: raiseOSError(osLastError()) diff --git a/tests/stdlib/tgetaddrinfo.nim b/tests/stdlib/tgetaddrinfo.nim new file mode 100644 index 000000000..39102e131 --- /dev/null +++ b/tests/stdlib/tgetaddrinfo.nim @@ -0,0 +1,36 @@ +discard """ + exitcode: 0 + output: "" +""" + +# bug: https://github.com/nim-lang/Nim/issues/10198 + +import nativesockets + +block DGRAM_UDP: + let aiList = getAddrInfo("127.0.0.1", 999.Port, AF_INET, SOCK_DGRAM, IPPROTO_UDP) + doAssert aiList != nil + doAssert aiList.ai_addr != nil + doAssert aiList.ai_addrlen == 16 + doAssert aiList.ai_next == nil + freeAddrInfo aiList + +when defined(posix): + + block RAW_ICMP: + # the port will be ignored + let aiList = getAddrInfo("127.0.0.1", 999.Port, AF_INET, SOCK_RAW, IPPROTO_ICMP) + doAssert aiList != nil + doAssert aiList.ai_addr != nil + doAssert aiList.ai_addrlen == 16 + doAssert aiList.ai_next == nil + freeAddrInfo aiList + + block RAW_ICMPV6: + # the port will be ignored + let aiList = getAddrInfo("::1", 999.Port, AF_INET6, SOCK_RAW, IPPROTO_ICMPV6) + doAssert aiList != nil + doAssert aiList.ai_addr != nil + doAssert aiList.ai_addrlen == 28 + doAssert aiList.ai_next == nil + freeAddrInfo aiList -- cgit 1.4.1-2-gfad0 From 6737634d88a70a3d87774c9f51f2ac6d2bf4da4f Mon Sep 17 00:00:00 2001 From: alaviss Date: Tue, 8 Jan 2019 18:41:15 +0700 Subject: os.execShellCmd: fixes #10231 (#10232) Darwin has long deprecated the wait union, but their macros still assume it unless you define _POSIX_C_SOURCE. This trips up C++ compilers. This commit duplicates the behavior of WEXITSTATUS when _POSIX_C_SOURCE is defined. --- lib/pure/os.nim | 4 +++- tests/stdlib/t10231.nim | 13 +++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 tests/stdlib/t10231.nim (limited to 'tests/stdlib') diff --git a/lib/pure/os.nim b/lib/pure/os.nim index a218121ed..3a959d4e2 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -1297,7 +1297,9 @@ proc execShellCmd*(command: string): int {.rtl, extern: "nos$1", ## the process has finished. To execute a program without having a ## shell involved, use the `execProcess` proc of the `osproc` ## module. - when defined(posix): + when defined(macosx): + result = c_system(command) shr 8 + elif defined(posix): result = WEXITSTATUS(c_system(command)) else: result = c_system(command) diff --git a/tests/stdlib/t10231.nim b/tests/stdlib/t10231.nim new file mode 100644 index 000000000..5d1101aa4 --- /dev/null +++ b/tests/stdlib/t10231.nim @@ -0,0 +1,13 @@ +discard """ + target: cpp + action: run + exitcode: 0 +""" + +import os + +if paramCount() == 0: + # main process + doAssert execShellCmd(getAppFilename().quoteShell & " test") == 1 +else: + quit 1 -- cgit 1.4.1-2-gfad0 From 9af85fb69f26dcae5fba7881a597d78f6bc7ffc8 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Sun, 13 Jan 2019 00:00:39 -0800 Subject: fixes #10273 execShellCmd now returns nonzero when child killed with signal + other fixes (#10274) * s/exitStatus(...)/exitStatusLikeShell(...)/ * fix #10273 execShellCmd now returns nonzero when child exits with signal * test case for #10249 and explanation for the bug * fix test failure * add tests/nim.cfg --- .gitignore | 2 +- lib/pure/os.nim | 21 ++++--- lib/pure/osproc.nim | 21 +++---- testament/lib/stdtest/specialpaths.nim | 31 ++++++++++ tests/nim.cfg | 1 + tests/stdlib/t10231.nim | 2 + tests/stdlib/tos.nim | 2 + tests/stdlib/tosproc.nim | 109 +++++++++++++++++++++++++++------ 8 files changed, 148 insertions(+), 41 deletions(-) create mode 100644 testament/lib/stdtest/specialpaths.nim create mode 100644 tests/nim.cfg (limited to 'tests/stdlib') diff --git a/.gitignore b/.gitignore index 132cd7f60..2bf0bf30c 100644 --- a/.gitignore +++ b/.gitignore @@ -32,7 +32,7 @@ doc/*.html doc/*.pdf doc/*.idx /web/upload -build/* +/build/* bin/* # iOS specific wildcards. diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 3a959d4e2..32fa2885e 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -1287,6 +1287,17 @@ proc moveFile*(source, dest: string) {.rtl, extern: "nos$1", discard tryRemoveFile(dest) raise +proc exitStatusLikeShell*(status: cint): cint = + ## converts exit code from `c_system` into a shell exit code + when defined(posix) and not weirdTarget: + if WIFSIGNALED(status): + # like the shell! + 128 + WTERMSIG(status) + else: + WEXITSTATUS(status) + else: + status + proc execShellCmd*(command: string): int {.rtl, extern: "nos$1", tags: [ExecIOEffect], noNimScript.} = ## Executes a `shell command`:idx:. @@ -1295,14 +1306,8 @@ proc execShellCmd*(command: string): int {.rtl, extern: "nos$1", ## line arguments given to program. The proc returns the error code ## of the shell when it has finished. The proc does not return until ## the process has finished. To execute a program without having a - ## shell involved, use the `execProcess` proc of the `osproc` - ## module. - when defined(macosx): - result = c_system(command) shr 8 - elif defined(posix): - result = WEXITSTATUS(c_system(command)) - else: - result = c_system(command) + ## shell involved, use `osproc.execProcess`. + result = exitStatusLikeShell(c_system(command)) # Templates for filtering directories and files when defined(windows) and not weirdTarget: diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index 72581f47c..dfe75d998 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -724,13 +724,6 @@ elif not defined(useNimRtl): proc isExitStatus(status: cint): bool = WIFEXITED(status) or WIFSIGNALED(status) - proc exitStatus(status: cint): cint = - if WIFSIGNALED(status): - # like the shell! - 128 + WTERMSIG(status) - else: - WEXITSTATUS(status) - proc envToCStringArray(t: StringTableRef): cstringArray = result = cast[cstringArray](alloc0((t.len + 1) * sizeof(cstring))) var i = 0 @@ -1054,7 +1047,7 @@ elif not defined(useNimRtl): proc waitForExit(p: Process, timeout: int = -1): int = if p.exitFlag: - return exitStatus(p.exitStatus) + return exitStatusLikeShell(p.exitStatus) if timeout == -1: var status: cint = 1 @@ -1109,7 +1102,7 @@ elif not defined(useNimRtl): finally: discard posix.close(kqFD) - result = exitStatus(p.exitStatus) + result = exitStatusLikeShell(p.exitStatus) else: import times @@ -1142,7 +1135,7 @@ elif not defined(useNimRtl): s.tv_nsec = b.tv_nsec if p.exitFlag: - return exitStatus(p.exitStatus) + return exitStatusLikeShell(p.exitStatus) if timeout == -1: var status: cint = 1 @@ -1220,20 +1213,20 @@ elif not defined(useNimRtl): if sigprocmask(SIG_UNBLOCK, nmask, omask) == -1: raiseOSError(osLastError()) - result = exitStatus(p.exitStatus) + result = exitStatusLikeShell(p.exitStatus) proc peekExitCode(p: Process): int = var status = cint(0) result = -1 if p.exitFlag: - return exitStatus(p.exitStatus) + return exitStatusLikeShell(p.exitStatus) var ret = waitpid(p.id, status, WNOHANG) if ret > 0: if isExitStatus(status): p.exitFlag = true p.exitStatus = status - result = exitStatus(status) + result = exitStatusLikeShell(status) proc createStream(stream: var Stream, handle: var FileHandle, fileMode: FileMode) = @@ -1265,7 +1258,7 @@ elif not defined(useNimRtl): proc execCmd(command: string): int = when defined(linux): let tmp = csystem(command) - result = if tmp == -1: tmp else: exitStatus(tmp) + result = if tmp == -1: tmp else: exitStatusLikeShell(tmp) else: result = csystem(command) diff --git a/testament/lib/stdtest/specialpaths.nim b/testament/lib/stdtest/specialpaths.nim new file mode 100644 index 000000000..3c8b2338c --- /dev/null +++ b/testament/lib/stdtest/specialpaths.nim @@ -0,0 +1,31 @@ +#[ +todo: move findNimStdLibCompileTime, findNimStdLib here +]# + +import os + +# 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 + +const nimRootDir* = sourcePath.parentDir.parentDir.parentDir.parentDir + ## root of Nim repo + +const stdlibDir* = nimRootDir / "lib" + # todo: make nimeval.findNimStdLibCompileTime use this + +const systemPath* = stdlibDir / "system.nim" + +const buildDir* = nimRootDir / "build" + ## refs #10268: all testament generated files should go here to avoid + ## polluting .gitignore + +static: + # sanity check + doAssert fileExists(systemPath) diff --git a/tests/nim.cfg b/tests/nim.cfg new file mode 100644 index 000000000..577baaacd --- /dev/null +++ b/tests/nim.cfg @@ -0,0 +1 @@ +--path:"../testament/lib" # so we can `import stdtest/foo` in this dir diff --git a/tests/stdlib/t10231.nim b/tests/stdlib/t10231.nim index 5d1101aa4..2bb64b475 100644 --- a/tests/stdlib/t10231.nim +++ b/tests/stdlib/t10231.nim @@ -6,6 +6,8 @@ discard """ import os +# consider moving this inside tosproc (taking care that it's for cpp mode) + if paramCount() == 0: # main process doAssert execShellCmd(getAppFilename().quoteShell & " test") == 1 diff --git a/tests/stdlib/tos.nim b/tests/stdlib/tos.nim index a7cf5d5b6..e4e14d5a1 100644 --- a/tests/stdlib/tos.nim +++ b/tests/stdlib/tos.nim @@ -263,3 +263,5 @@ block splitFile: doAssert splitFile("abc/.") == ("abc", ".", "") doAssert splitFile("..") == ("", "..", "") doAssert splitFile("a/..") == ("a", "..", "") + +# execShellCmd is tested in tosproc diff --git a/tests/stdlib/tosproc.nim b/tests/stdlib/tosproc.nim index 9d57d4574..7a7c1836f 100644 --- a/tests/stdlib/tosproc.nim +++ b/tests/stdlib/tosproc.nim @@ -1,23 +1,96 @@ -discard """ - output: "" -""" # test the osproc module -import os, osproc +import stdtest/specialpaths +import "../.." / compiler/unittest_light -block execProcessTest: - let dir = parentDir(currentSourcePath()) - let (outp, err) = execCmdEx("nim c " & quoteShell(dir / "osproctest.nim")) - doAssert err == 0 - let exePath = dir / addFileExt("osproctest", ExeExt) - let outStr1 = execProcess(exePath, workingDir=dir, args=["foo", "b A r"], options={}) - doAssert outStr1 == dir & "\nfoo\nb A r\n" +when defined(case_testfile): # compiled test file for child process + from posix import exitnow + proc c_exit2(code: c_int): void {.importc: "_exit", header: "".} + import os + var a = 0 + proc fun(b = 0) = + a.inc + if a mod 10000000 == 0: # prevents optimizing it away + echo a + fun(b+1) - const testDir = "t e st" - createDir(testDir) - doAssert dirExists(testDir) - let outStr2 = execProcess(exePath, workingDir=testDir, args=["x yz"], options={}) - doAssert outStr2 == absolutePath(testDir) & "\nx yz\n" + proc main() = + let args = commandLineParams() + echo (msg: "child binary", pid: getCurrentProcessId()) + let arg = args[0] + echo (arg: arg) + case arg + of "exit_0": + if true: quit(0) + of "exitnow_139": + if true: exitnow(139) + of "c_exit2_139": + if true: c_exit2(139) + of "quit_139": + # `exitStatusLikeShell` doesn't distinguish between a process that + # exit(139) and a process that gets killed with `SIGSEGV` because + # 139 = 11 + 128 = SIGSEGV + 128. + # However, as #10249 shows, this leads to bad debugging experience + # when a child process dies with SIGSEGV, leaving no trace of why it + # failed. The shell (and lldb debugger) solves that by inserting a + # helpful msg: `segmentation fault` when it detects a signal killed + # the child. + # todo: expose an API that will show more diagnostic, returing + # (exitCode, signal) instead of just `shellExitCode`. + if true: quit(139) + of "exit_recursion": # stack overflow by infinite recursion + fun() + echo a + of "exit_array": # bad array access + echo args[1] + main() - removeDir(testDir) - removeFile(exePath) +else: + + import os, osproc, strutils, posix + + block execShellCmdTest: + ## first, compile child program + const nim = getCurrentCompilerExe() + const sourcePath = currentSourcePath() + let output = buildDir / "D20190111T024543".addFileExt(ExeExt) + let cmd = "$# c -o:$# -d:release -d:case_testfile $#" % [nim, output, + sourcePath] + # we're testing `execShellCmd` so don't rely on it to compile test file + # note: this should be exported in posix.nim + proc c_system(cmd: cstring): cint {.importc: "system", + header: "".} + assertEquals c_system(cmd), 0 + + ## use it + template runTest(arg: string, expected: int) = + echo (arg2: arg, expected2: expected) + assertEquals execShellCmd(output & " " & arg), expected + + runTest("exit_0", 0) + runTest("exitnow_139", 139) + runTest("c_exit2_139", 139) + runTest("quit_139", 139) + runTest("exit_array", 1) + when defined(posix): # on windows, -1073741571 + runTest("exit_recursion", SIGSEGV.int + 128) # bug #10273: was returning 0 + assertEquals exitStatusLikeShell(SIGSEGV), SIGSEGV + 128.cint + + block execProcessTest: + let dir = parentDir(currentSourcePath()) + let (outp, err) = execCmdEx("nim c " & quoteShell(dir / "osproctest.nim")) + doAssert err == 0 + let exePath = dir / addFileExt("osproctest", ExeExt) + let outStr1 = execProcess(exePath, workingDir = dir, args = ["foo", + "b A r"], options = {}) + doAssert outStr1 == dir & "\nfoo\nb A r\n" + + const testDir = "t e st" + createDir(testDir) + doAssert dirExists(testDir) + let outStr2 = execProcess(exePath, workingDir = testDir, args = ["x yz"], + options = {}) + doAssert outStr2 == absolutePath(testDir) & "\nx yz\n" + + removeDir(testDir) + removeFile(exePath) -- cgit 1.4.1-2-gfad0 From 796432ff9439a0d0137f342162d78cf11f2d27a5 Mon Sep 17 00:00:00 2001 From: Araq Date: Sun, 13 Jan 2019 13:54:44 +0100 Subject: make tests more robust; tests should be deterministic, no randomize() calls in tests --- tests/stdlib/tmath.nim | 24 ++++++++++++------------ tests/stdlib/tosproc.nim | 5 ++++- tests/stdlib/tunittest.nim | 1 - 3 files changed, 16 insertions(+), 14 deletions(-) (limited to 'tests/stdlib') diff --git a/tests/stdlib/tmath.nim b/tests/stdlib/tmath.nim index 544fa8dc0..bdb5aa332 100644 --- a/tests/stdlib/tmath.nim +++ b/tests/stdlib/tmath.nim @@ -20,29 +20,29 @@ import sets, tables suite "random int": test "there might be some randomness": var set = initSet[int](128) - randomize() + for i in 1..1000: incl(set, random(high(int))) check len(set) == 1000 test "single number bounds work": - randomize() + var rand: int for i in 1..1000: rand = random(1000) check rand < 1000 check rand > -1 test "slice bounds work": - randomize() + var rand: int for i in 1..1000: rand = random(100..1000) check rand < 1000 check rand >= 100 - test "randomize() again gives new numbers": - randomize() + test " again gives new numbers": + var rand1 = random(1000000) os.sleep(200) - randomize() + var rand2 = random(1000000) check rand1 != rand2 @@ -50,29 +50,29 @@ suite "random int": suite "random float": test "there might be some randomness": var set = initSet[float](128) - randomize() + for i in 1..100: incl(set, random(1.0)) check len(set) == 100 test "single number bounds work": - randomize() + var rand: float for i in 1..1000: rand = random(1000.0) check rand < 1000.0 check rand > -1.0 test "slice bounds work": - randomize() + var rand: float for i in 1..1000: rand = random(100.0..1000.0) check rand < 1000.0 check rand >= 100.0 - test "randomize() again gives new numbers": - randomize() + test " again gives new numbers": + var rand1:float = random(1000000.0) os.sleep(200) - randomize() + var rand2:float = random(1000000.0) check rand1 != rand2 diff --git a/tests/stdlib/tosproc.nim b/tests/stdlib/tosproc.nim index 7a7c1836f..b8d3be9bb 100644 --- a/tests/stdlib/tosproc.nim +++ b/tests/stdlib/tosproc.nim @@ -93,4 +93,7 @@ else: doAssert outStr2 == absolutePath(testDir) & "\nx yz\n" removeDir(testDir) - removeFile(exePath) + try: + removeFile(exePath) + except OSError: + discard diff --git a/tests/stdlib/tunittest.nim b/tests/stdlib/tunittest.nim index c8656bbff..65baefef0 100644 --- a/tests/stdlib/tunittest.nim +++ b/tests/stdlib/tunittest.nim @@ -50,7 +50,6 @@ test "unittest multiple requires": import math, random from strutils import parseInt proc defectiveRobot() = - randomize() case random(1..4) of 1: raise newException(OSError, "CANNOT COMPUTE!") of 2: discard parseInt("Hello World!") -- cgit 1.4.1-2-gfad0 From d69a7842fa0836ca18b8057a33bca81a771a9e5a Mon Sep 17 00:00:00 2001 From: Araq Date: Mon, 14 Jan 2019 12:36:34 +0100 Subject: fixes #7878 --- compiler/ccgexprs.nim | 2 +- tests/stdlib/trepr.nim | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) (limited to 'tests/stdlib') diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index d00371dd8..ed6255004 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -1501,7 +1501,7 @@ proc genRepr(p: BProc, e: PNode, d: var TLoc) = addrLoc(p.config, a), genTypeInfo(p.module, t, e.info)]), a.storage) of tyOpenArray, tyVarargs: var b: TLoc - case a.t.kind + case skipTypes(a.t, abstractVarRange).kind of tyOpenArray, tyVarargs: putIntoDest(p, b, e, "$1, $1Len_0" % [rdLoc(a)], a.storage) of tyString, tySequence: diff --git a/tests/stdlib/trepr.nim b/tests/stdlib/trepr.nim index 33cb581ef..c1941bd38 100644 --- a/tests/stdlib/trepr.nim +++ b/tests/stdlib/trepr.nim @@ -1,5 +1,6 @@ discard """ - output: "{a, b}{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}" + output: '''{a, b}{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'} +[1, 2, 3, 4, 5, 6]''' """ type @@ -25,3 +26,11 @@ when false: # "a", "b", "c", "d", "e" #] #echo(repr(testseq)) + +# bug #7878 +proc test(variable: var openarray[int]) = + echo repr(variable) + +var arr = [1, 2, 3, 4, 5, 6] + +test(arr) -- cgit 1.4.1-2-gfad0 From b78af990b8f656cb17044c96609f58e1f01e6a01 Mon Sep 17 00:00:00 2001 From: Arne Döring Date: Mon, 14 Jan 2019 17:16:17 +0100 Subject: Fixes #10065 (#10260) CountTable now returns 0 instead of 'key not found' for get requests. --- lib/pure/collections/tables.nim | 97 ++++++++++++++++++----------------------- tests/stdlib/tmget.nim | 8 ++-- 2 files changed, 47 insertions(+), 58 deletions(-) (limited to 'tests/stdlib') diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index f46a368b1..1fa2ca0a6 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -933,52 +933,6 @@ proc rawGet[A](t: CountTable[A], key: A): int = h = nextTry(h, high(t.data)) result = -1 - h # < 0 => MISSING; insert idx = -1 - result -template ctget(t, key: untyped): untyped = - var index = rawGet(t, key) - if index >= 0: result = t.data[index].val - else: - when compiles($key): - raise newException(KeyError, "key not found: " & $key) - else: - raise newException(KeyError, "key not found") - -proc `[]`*[A](t: CountTable[A], key: A): int {.deprecatedGet.} = - ## retrieves the value at ``t[key]``. If ``key`` is not in ``t``, - ## the ``KeyError`` exception is raised. One can check with ``hasKey`` - ## whether the key exists. - ctget(t, key) - -proc `[]`*[A](t: var CountTable[A], key: A): var int {.deprecatedGet.} = - ## retrieves the value at ``t[key]``. The value can be modified. - ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. - ctget(t, key) - -proc mget*[A](t: var CountTable[A], key: A): var int {.deprecated.} = - ## retrieves the value at ``t[key]``. The value can be modified. - ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. - ## Use ``[]`` instead. - ctget(t, key) - -proc getOrDefault*[A](t: CountTable[A], key: A): int = - ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. Otherwise, 0 (the - ## default initialization value of ``int``), is returned. - var index = rawGet(t, key) - if index >= 0: result = t.data[index].val - -proc getOrDefault*[A](t: CountTable[A], key: A, default: int): int = - ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. Otherwise, the - ## integer value of ``default`` is returned. - var index = rawGet(t, key) - result = if index >= 0: t.data[index].val else: default - -proc hasKey*[A](t: CountTable[A], key: A): bool = - ## returns true iff ``key`` is in the table ``t``. - result = rawGet(t, key) >= 0 - -proc contains*[A](t: CountTable[A], key: A): bool = - ## Alias of ``hasKey`` for use with the ``in`` operator. - return hasKey[A](t, key) - proc rawInsert[A](t: CountTable[A], data: var seq[tuple[key: A, val: int]], key: A, val: int) = var h: Hash = hash(key) and high(data) @@ -996,16 +950,40 @@ proc enlarge[A](t: var CountTable[A]) = proc `[]=`*[A](t: var CountTable[A], key: A, val: int) = ## puts a ``(key, value)`` pair into ``t``. assert val >= 0 - var h = rawGet(t, key) + let h = rawGet(t, key) if h >= 0: t.data[h].val = val else: if mustRehash(len(t.data), t.counter): enlarge(t) rawInsert(t, t.data, key, val) inc(t.counter) - #h = -1 - h - #t.data[h].key = key - #t.data[h].val = val + +template ctget(t, key, default: untyped): untyped = + var index = rawGet(t, key) + result = if index >= 0: t.data[index].val else: default + +proc `[]`*[A](t: CountTable[A], key: A): int = + ## Retrieves the value at ``t[key]`` if ``key`` is in ``t``. + ## Otherwise ``0`` is returned. + ctget(t, key, 0) + +proc mget*[A](t: var CountTable[A], key: A): var int = + ## Retrieves the value at ``t[key]``. The value can be modified. + ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. + get(t, key) + +proc getOrDefault*[A](t: CountTable[A], key: A; default: int = 0): int = + ## Retrieves the value at ``t[key]`` if``key`` is in ``t``. Otherwise, the + ## integer value of ``default`` is returned. + ctget(t, key, default) + +proc hasKey*[A](t: CountTable[A], key: A): bool = + ## returns true iff ``key`` is in the table ``t``. + result = rawGet(t, key) >= 0 + +proc contains*[A](t: CountTable[A], key: A): bool = + ## Alias of ``hasKey`` for use with the ``in`` operator. + return hasKey[A](t, key) proc inc*[A](t: var CountTable[A], key: A, val = 1) = ## increments ``t[key]`` by ``val``. @@ -1113,16 +1091,15 @@ iterator mvalues*[A](t: CountTableRef[A]): var int = for h in 0..high(t.data): if t.data[h].val != 0: yield t.data[h].val -proc `[]`*[A](t: CountTableRef[A], key: A): var int {.deprecatedGet.} = +proc `[]`*[A](t: CountTableRef[A], key: A): int = ## retrieves the value at ``t[key]``. The value can be modified. ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. result = t[][key] -proc mget*[A](t: CountTableRef[A], key: A): var int {.deprecated.} = +proc mget*[A](t: CountTableRef[A], key: A): var int = ## retrieves the value at ``t[key]``. The value can be modified. ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. - ## Use ``[]`` instead. - result = t[][key] + mget(t[], key) proc getOrDefault*[A](t: CountTableRef[A], key: A): int = ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. Otherwise, 0 (the @@ -1394,6 +1371,18 @@ when isMainModule: let t = toCountTable([0, 0, 5, 5, 5]) doAssert t.smallest == (0, 2) + block: #10065 + let t = toCountTable("abracadabra") + doAssert t['z'] == 0 + + var t_mut = toCountTable("abracadabra") + doAssert t_mut['z'] == 0 + # the previous read may not have modified the table. + doAssert t_mut.hasKey('z') == false + t_mut['z'] = 1 + doAssert t_mut['z'] == 1 + doAssert t_mut.hasKey('z') == true + block: var tp: Table[string, string] = initTable[string, string]() doAssert "test1" == tp.getOrDefault("test1", "test1") diff --git a/tests/stdlib/tmget.nim b/tests/stdlib/tmget.nim index 5792b6282..5e2e327f4 100644 --- a/tests/stdlib/tmget.nim +++ b/tests/stdlib/tmget.nim @@ -11,10 +11,10 @@ Can't access 6 Can't access 6 10 11 -Can't access 6 +0 10 11 -Can't access 6 +0 10 11 Can't access 6 @@ -85,7 +85,7 @@ block: except KeyError: echo "Can't access 6" echo x[5] - x[5] += 1 + x.inc 5, 1 var c = x[5] echo c @@ -97,7 +97,7 @@ block: except KeyError: echo "Can't access 6" echo x[5] - x[5] += 1 + x.inc 5, 1 var c = x[5] echo c -- cgit 1.4.1-2-gfad0 From b8454327c52faa82632cc90dd8fa326efbb38565 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Wed, 16 Jan 2019 01:16:14 -0800 Subject: json: support tuple (#10010) --- lib/pure/json.nim | 17 ++++++++++++++++- tests/stdlib/tjsonmacro.nim | 4 ++++ 2 files changed, 20 insertions(+), 1 deletion(-) (limited to 'tests/stdlib') diff --git a/lib/pure/json.nim b/lib/pure/json.nim index 010dd8f70..02fa6dc67 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -142,7 +142,8 @@ runnableExamples: doAssert $(%* Foo()) == """{"a1":0,"a2":0,"a0":0,"a3":0,"a4":0}""" import - hashes, tables, strutils, lexbase, streams, unicode, macros, parsejson + hashes, tables, strutils, lexbase, streams, unicode, macros, parsejson, + typetraits export tables.`$` @@ -356,6 +357,20 @@ when false: assert false notin elements, "usage error: only empty sets allowed" assert true notin elements, "usage error: only empty sets allowed" +#[ +Note: could use simply: +proc `%`*(o: object|tuple): JsonNode +but blocked by https://github.com/nim-lang/Nim/issues/10019 +]# +proc `%`*(o: tuple): JsonNode = + ## Generic constructor for JSON data. Creates a new `JObject JsonNode` + when isNamedTuple(type(o)): + result = newJObject() + for k, v in o.fieldPairs: result[k] = %v + else: + result = newJArray() + for a in o.fields: result.add(%a) + proc `%`*(o: object): JsonNode = ## Generic constructor for JSON data. Creates a new `JObject JsonNode` result = newJObject() diff --git a/tests/stdlib/tjsonmacro.nim b/tests/stdlib/tjsonmacro.nim index 33332447b..2e95b4833 100644 --- a/tests/stdlib/tjsonmacro.nim +++ b/tests/stdlib/tjsonmacro.nim @@ -516,3 +516,7 @@ when true: var w = u.to(MyDistRef) doAssert v.name == "smith" doAssert MyRef(w).name == "smith" + + block test_tuple: + doAssert $(%* (a1: 10, a2: "foo")) == """{"a1":10,"a2":"foo"}""" + doAssert $(%* (10, "foo")) == """[10,"foo"]""" -- cgit 1.4.1-2-gfad0 From 095eaacf21a80c72d15ba9a7a422ddc192124ae1 Mon Sep 17 00:00:00 2001 From: Federico Ceratto Date: Sat, 19 Jan 2019 15:01:27 +0000 Subject: Fix spelling errors (#10379) --- compiler/semexprs.nim | 2 +- lib/pure/includes/osseps.nim | 4 ++-- lib/pure/net.nim | 38 +++++++++++++++++++------------------- tests/stdlib/ttimes.nim | 2 +- 4 files changed, 23 insertions(+), 23 deletions(-) (limited to 'tests/stdlib') diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 6f2981f63..5e1e4cbbd 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -230,7 +230,7 @@ proc semConv(c: PContext, n: PNode): PNode = # special case to make MyObject(x = 3) produce a nicer error message: if n[1].kind == nkExprEqExpr and targetType.skipTypes(abstractPtrs).kind == tyObject: - localError(c.config, n.info, "object contruction uses ':', not '='") + localError(c.config, n.info, "object construction uses ':', not '='") var op = semExprWithType(c, n.sons[1]) if targetType.isMetaType: let final = inferWithMetatype(c, targetType, op, true) diff --git a/lib/pure/includes/osseps.nim b/lib/pure/includes/osseps.nim index 9a79fe303..944ad123e 100644 --- a/lib/pure/includes/osseps.nim +++ b/lib/pure/includes/osseps.nim @@ -85,9 +85,9 @@ elif doslikeFileSystem: const CurDir* = '.' ParDir* = ".." - DirSep* = '\\' # seperator within paths + DirSep* = '\\' # separator within paths AltSep* = '/' - PathSep* = ';' # seperator between paths + PathSep* = ';' # separator between paths FileSystemCaseSensitive* = false ExeExt* = "exe" ScriptExt* = "bat" diff --git a/lib/pure/net.nim b/lib/pure/net.nim index d4c5a88b7..a5bdf3ce6 100644 --- a/lib/pure/net.nim +++ b/lib/pure/net.nim @@ -233,7 +233,7 @@ proc parseIPv4Address(addressStr: string): IpAddress = var byteCount = 0 currentByte:uint16 = 0 - seperatorValid = false + separatorValid = false result.family = IpAddressFamily.IPv4 @@ -244,20 +244,20 @@ proc parseIPv4Address(addressStr: string): IpAddress = if currentByte > 255'u16: raise newException(ValueError, "Invalid IP Address. Value is out of range") - seperatorValid = true + separatorValid = true elif addressStr[i] == '.': # IPv4 address separator - if not seperatorValid or byteCount >= 3: + if not separatorValid or byteCount >= 3: raise newException(ValueError, "Invalid IP Address. The address consists of too many groups") result.address_v4[byteCount] = cast[uint8](currentByte) currentByte = 0 byteCount.inc - seperatorValid = false + separatorValid = false else: raise newException(ValueError, "Invalid IP Address. Address contains an invalid character") - if byteCount != 3 or not seperatorValid: + if byteCount != 3 or not separatorValid: raise newException(ValueError, "Invalid IP Address") result.address_v4[byteCount] = cast[uint8](currentByte) @@ -272,7 +272,7 @@ proc parseIPv6Address(addressStr: string): IpAddress = groupCount = 0 currentGroupStart = 0 currentShort:uint32 = 0 - seperatorValid = true + separatorValid = true dualColonGroup = -1 lastWasColon = false v4StartPos = -1 @@ -280,15 +280,15 @@ proc parseIPv6Address(addressStr: string): IpAddress = for i,c in addressStr: if c == ':': - if not seperatorValid: + if not separatorValid: raise newException(ValueError, - "Invalid IP Address. Address contains an invalid seperator") + "Invalid IP Address. Address contains an invalid separator") if lastWasColon: if dualColonGroup != -1: raise newException(ValueError, - "Invalid IP Address. Address contains more than one \"::\" seperator") + "Invalid IP Address. Address contains more than one \"::\" separator") dualColonGroup = groupCount - seperatorValid = false + separatorValid = false elif i != 0 and i != high(addressStr): if groupCount >= 8: raise newException(ValueError, @@ -297,7 +297,7 @@ proc parseIPv6Address(addressStr: string): IpAddress = result.address_v6[groupCount*2+1] = cast[uint8](currentShort and 0xFF) currentShort = 0 groupCount.inc() - if dualColonGroup != -1: seperatorValid = false + if dualColonGroup != -1: separatorValid = false elif i == 0: # only valid if address starts with :: if addressStr[1] != ':': raise newException(ValueError, @@ -309,11 +309,11 @@ proc parseIPv6Address(addressStr: string): IpAddress = lastWasColon = true currentGroupStart = i + 1 elif c == '.': # Switch to parse IPv4 mode - if i < 3 or not seperatorValid or groupCount >= 7: + if i < 3 or not separatorValid or groupCount >= 7: raise newException(ValueError, "Invalid IP Address") v4StartPos = currentGroupStart currentShort = 0 - seperatorValid = false + separatorValid = false break elif c in strutils.HexDigits: if c in strutils.Digits: # Normal digit @@ -326,14 +326,14 @@ proc parseIPv6Address(addressStr: string): IpAddress = raise newException(ValueError, "Invalid IP Address. Value is out of range") lastWasColon = false - seperatorValid = true + separatorValid = true else: raise newException(ValueError, "Invalid IP Address. Address contains an invalid character") if v4StartPos == -1: # Don't parse v4. Copy the remaining v6 stuff - if seperatorValid: # Copy remaining data + if separatorValid: # Copy remaining data if groupCount >= 8: raise newException(ValueError, "Invalid IP Address. The address consists of too many groups") @@ -347,19 +347,19 @@ proc parseIPv6Address(addressStr: string): IpAddress = if currentShort > 255'u32: raise newException(ValueError, "Invalid IP Address. Value is out of range") - seperatorValid = true + separatorValid = true elif c == '.': # IPv4 address separator - if not seperatorValid or byteCount >= 3: + if not separatorValid or byteCount >= 3: raise newException(ValueError, "Invalid IP Address") result.address_v6[groupCount*2 + byteCount] = cast[uint8](currentShort) currentShort = 0 byteCount.inc() - seperatorValid = false + separatorValid = false else: # Invalid character raise newException(ValueError, "Invalid IP Address. Address contains an invalid character") - if byteCount != 3 or not seperatorValid: + if byteCount != 3 or not separatorValid: raise newException(ValueError, "Invalid IP Address") result.address_v6[groupCount*2 + byteCount] = cast[uint8](currentShort) groupCount += 2 diff --git a/tests/stdlib/ttimes.nim b/tests/stdlib/ttimes.nim index 32f39953d..3999c968f 100644 --- a/tests/stdlib/ttimes.nim +++ b/tests/stdlib/ttimes.nim @@ -253,7 +253,7 @@ suite "ttimes": parseTestExcp("+1", "mm") parseTestExcp("+1", "ss") - test "_ as a seperator": + test "_ as a separator": discard parse("2000_01_01", "YYYY'_'MM'_'dd") test "dynamic timezone": -- cgit 1.4.1-2-gfad0 From 0d480bfe22651edc9bdf7c5976668b47eb61b0a5 Mon Sep 17 00:00:00 2001 From: Ico Doornekamp Date: Wed, 23 Jan 2019 09:16:14 +0100 Subject: Added basic bit manipulation procs to bitops (#10338) --- lib/pure/bitops.nim | 60 +++++++++++++++++++++++++++++++++++++++++++++++- tests/stdlib/tbitops.nim | 57 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 1 deletion(-) (limited to 'tests/stdlib') diff --git a/lib/pure/bitops.nim b/lib/pure/bitops.nim index 3f213c5ea..d258a42de 100644 --- a/lib/pure/bitops.nim +++ b/lib/pure/bitops.nim @@ -8,7 +8,8 @@ # ## This module implements a series of low level methods for bit manipulation. -## By default, this module use compiler intrinsics to improve performance + +## By default, this module use compiler intrinsics where possible to improve performance ## on supported compilers: ``GCC``, ``LLVM_GCC``, ``CLANG``, ``VCC``, ``ICC``. ## ## The module will fallback to pure nim procs incase the backend is not supported. @@ -32,6 +33,63 @@ const useICC_builtins = defined(icc) and useBuiltins const useVCC_builtins = defined(vcc) and useBuiltins const arch64 = sizeof(int) == 8 +when defined(nimHasalignOf): + + import macros + + type BitsRange*[T] = range[0..sizeof(T)*8-1] + ## Returns a range with all bit positions for type ``T`` + + proc setMask*[T: SomeInteger](v: var T, mask: T) {.inline.} = + ## Returns ``v``, with all the ``1`` bits from ``mask`` set to 1 + v = v or mask + + proc clearMask*[T: SomeInteger](v: var T, mask: T) {.inline.} = + ## Returns ``v``, with all the ``1`` bits from ``mask`` set to 0 + v = v and not mask + + proc flipMask*[T: SomeInteger](v: var T, mask: T) {.inline.} = + ## Returns ``v``, with all the ``1`` bits from ``mask`` flipped + v = v xor mask + + proc setBit*[T: SomeInteger](v: var T, bit: BitsRange[T]) {.inline.} = + ## Returns ``v``, with the bit at position ``bit`` set to 1 + v.setMask(1.T shl bit) + + proc clearBit*[T: SomeInteger](v: var T, bit: BitsRange[T]) {.inline.} = + ## Returns ``v``, with the bit at position ``bit`` set to 0 + v.clearMask(1.T shl bit) + + proc flipBit*[T: SomeInteger](v: var T, bit: BitsRange[T]) {.inline.} = + ## Returns ``v``, with the bit at position ``bit`` flipped + v.flipMask(1.T shl bit) + + macro setBits*(v: typed, bits: varargs[typed]): untyped = + ## Returns ``v``, with the bits at positions ``bits`` set to 1 + bits.expectKind(nnkBracket) + result = newStmtList() + for bit in bits: + result.add newCall("setBit", v, bit) + + macro clearBits*(v: typed, bits: varargs[typed]): untyped = + ## Returns ``v``, with the bits at positions ``bits`` set to 0 + bits.expectKind(nnkBracket) + result = newStmtList() + for bit in bits: + result.add newCall("clearBit", v, bit) + + macro flipBits*(v: typed, bits: varargs[typed]): untyped = + ## Returns ``v``, with the bits at positions ``bits`` set to 0 + bits.expectKind(nnkBracket) + result = newStmtList() + for bit in bits: + result.add newCall("flipBit", v, bit) + + proc testBit*[T: SomeInteger](v: var T, bit: BitsRange[T]): bool {.inline.} = + ## Returns true if the bit in ``v`` at positions ``bit`` is set to 1 + let mask = 1.T shl bit + return (v and mask) == mask + # #### Pure Nim version #### proc firstSetBit_nim(x: uint32): int {.inline, nosideeffect.} = diff --git a/tests/stdlib/tbitops.nim b/tests/stdlib/tbitops.nim index d8c6da1d4..b8b44703c 100644 --- a/tests/stdlib/tbitops.nim +++ b/tests/stdlib/tbitops.nim @@ -162,6 +162,63 @@ proc main() = doAssert( U64A.rotateLeftBits(64) == U64A) doAssert( U64A.rotateRightBits(64) == U64A) + block: + # mask operations + var v: uint8 + v.setMask(0b1100_0000) + v.setMask(0b0000_1100) + doAssert(v == 0b1100_1100) + v.flipMask(0b0101_0101) + doAssert(v == 0b1001_1001) + v.clearMask(0b1000_1000) + doAssert(v == 0b0001_0001) + v.clearMask(0b0001_0001) + doAssert(v == 0b0000_0000) + block: + # single bit operations + var v: uint8 + v.setBit(0) + doAssert v == 0x0000_0001 + v.setBit(1) + doAssert v == 0b0000_0011 + v.flipBit(7) + doAssert v == 0b1000_0011 + v.clearBit(0) + doAssert v == 0b1000_0010 + v.flipBit(1) + doAssert v == 0b1000_0000 + doAssert v.testbit(7) + doAssert not v.testbit(6) + block: + # multi bit operations + var v: uint8 + v.setBits(0, 1, 7) + doAssert v == 0b1000_0011 + v.flipBits(2, 3) + doAssert v == 0b1000_1111 + v.clearBits(7, 0, 1) + doAssert v == 0b0000_1100 + block: + # signed + var v: int8 + v.setBit(7) + doAssert v == -128 + block: + var v: uint64 + v.setBit(63) + doAssert v == 0b1000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000'u64 + block: + # Test if RangeError is thrown if indexing out of range + try: + var v: uint32 + var i = 32 + v.setBit(i) + doAssert false + except RangeError: + discard + except: + doAssert false + echo "OK" main() -- cgit 1.4.1-2-gfad0 From f0be575ed189ac5ac69a700e8387e60a0e2ebca7 Mon Sep 17 00:00:00 2001 From: narimiran Date: Wed, 23 Jan 2019 15:23:39 +0100 Subject: move tests from `tospaths` to `tos`, fixes #9671 Also, change some of `echo`s to `doAssert`. --- tests/stdlib/tos.nim | 154 +++++++++++++++++++++++++++++++++------------- tests/stdlib/tospaths.nim | 99 ----------------------------- 2 files changed, 111 insertions(+), 142 deletions(-) delete mode 100644 tests/stdlib/tospaths.nim (limited to 'tests/stdlib') diff --git a/tests/stdlib/tos.nim b/tests/stdlib/tos.nim index e4e14d5a1..23fa4d098 100644 --- a/tests/stdlib/tos.nim +++ b/tests/stdlib/tos.nim @@ -1,13 +1,5 @@ discard """ - output: '''true -true -true -true -true -true -true -true -true + output: ''' All: __really_obscure_dir_name/are.x __really_obscure_dir_name/created @@ -27,31 +19,13 @@ __really_obscure_dir_name/created __really_obscure_dir_name/dirs __really_obscure_dir_name/some __really_obscure_dir_name/test -false -false -false -false -false -false -false -false -false -true -true Raises Raises -true -true -true -true -true -true - ''' """ # test os path creation, iteration, and deletion -import os, strutils +import os, strutils, pathnorm block fileOperations: let files = @["these.txt", "are.x", "testing.r", "files.q"] @@ -60,17 +34,17 @@ block fileOperations: let dname = "__really_obscure_dir_name" createDir(dname) - echo dirExists(dname) + doAssert dirExists(dname) # Test creating files and dirs for dir in dirs: createDir(dname/dir) - echo dirExists(dname/dir) + doAssert dirExists(dname/dir) for file in files: let fh = open(dname/file, fmReadWrite) fh.close() - echo fileExists(dname/file) + doAssert fileExists(dname/file) echo "All:" @@ -93,23 +67,23 @@ block fileOperations: # Test removal of files dirs for dir in dirs: removeDir(dname/dir) - echo dirExists(dname/dir) + doAssert: not dirExists(dname/dir) for file in files: removeFile(dname/file) - echo fileExists(dname/file) + doAssert: not fileExists(dname/file) removeDir(dname) - echo dirExists(dname) + doAssert: not dirExists(dname) # createDir should create recursive directories createDir(dirs[0] / dirs[1]) - echo dirExists(dirs[0] / dirs[1]) # true + doAssert dirExists(dirs[0] / dirs[1]) # true removeDir(dirs[0]) # createDir should properly handle trailing separator createDir(dname / "") - echo dirExists(dname) # true + doAssert dirExists(dname) # true removeDir(dname) # createDir should raise IOError if the path exists @@ -138,10 +112,10 @@ block fileOperations: copyDir("a", "../dest/a") removeDir("a") - echo dirExists("../dest/a/b") - echo fileExists("../dest/a/b/file.txt") + doAssert dirExists("../dest/a/b") + doAssert fileExists("../dest/a/b/file.txt") - echo fileExists("../dest/a/b/c/fileC.txt") + doAssert fileExists("../dest/a/b/c/fileC.txt") removeDir("../dest") # test copyDir: @@ -152,8 +126,8 @@ block fileOperations: copyDir("a/", "../dest/a/") removeDir("a") - echo dirExists("../dest/a/b") - echo fileExists("../dest/a/file.txt") + doAssert dirExists("../dest/a/b") + doAssert fileExists("../dest/a/file.txt") removeDir("../dest") import times @@ -165,9 +139,9 @@ block modificationTime: setLastModificationTime("a", tm) when defined(macosx): - echo "true" + doAssert true else: - echo getLastModificationTime("a") == tm + doAssert getLastModificationTime("a") == tm removeFile("a") block walkDirRec: @@ -265,3 +239,97 @@ block splitFile: doAssert splitFile("a/..") == ("a", "..", "") # execShellCmd is tested in tosproc + +block ospaths: + doAssert unixToNativePath("") == "" + doAssert unixToNativePath(".") == $CurDir + doAssert unixToNativePath("..") == $ParDir + doAssert isAbsolute(unixToNativePath("/")) + doAssert isAbsolute(unixToNativePath("/", "a")) + doAssert isAbsolute(unixToNativePath("/a")) + doAssert isAbsolute(unixToNativePath("/a", "a")) + doAssert isAbsolute(unixToNativePath("/a/b")) + doAssert isAbsolute(unixToNativePath("/a/b", "a")) + doAssert unixToNativePath("a/b") == joinPath("a", "b") + + when defined(macos): + doAssert unixToNativePath("./") == ":" + doAssert unixToNativePath("./abc") == ":abc" + doAssert unixToNativePath("../abc") == "::abc" + doAssert unixToNativePath("../../abc") == ":::abc" + doAssert unixToNativePath("/abc", "a") == "abc" + doAssert unixToNativePath("/abc/def", "a") == "abc:def" + elif doslikeFileSystem: + doAssert unixToNativePath("./") == ".\\" + doAssert unixToNativePath("./abc") == ".\\abc" + doAssert unixToNativePath("../abc") == "..\\abc" + doAssert unixToNativePath("../../abc") == "..\\..\\abc" + doAssert unixToNativePath("/abc", "a") == "a:\\abc" + doAssert unixToNativePath("/abc/def", "a") == "a:\\abc\\def" + else: + #Tests for unix + doAssert unixToNativePath("./") == "./" + doAssert unixToNativePath("./abc") == "./abc" + doAssert unixToNativePath("../abc") == "../abc" + doAssert unixToNativePath("../../abc") == "../../abc" + doAssert unixToNativePath("/abc", "a") == "/abc" + doAssert unixToNativePath("/abc/def", "a") == "/abc/def" + + block extractFilenameTest: + doAssert extractFilename("") == "" + when defined(posix): + doAssert extractFilename("foo/bar") == "bar" + doAssert extractFilename("foo/bar.txt") == "bar.txt" + doAssert extractFilename("foo/") == "" + doAssert extractFilename("/") == "" + when doslikeFileSystem: + doAssert extractFilename(r"foo\bar") == "bar" + doAssert extractFilename(r"foo\bar.txt") == "bar.txt" + doAssert extractFilename(r"foo\") == "" + doAssert extractFilename(r"C:\") == "" + + block lastPathPartTest: + doAssert lastPathPart("") == "" + when defined(posix): + doAssert lastPathPart("foo/bar.txt") == "bar.txt" + doAssert lastPathPart("foo/") == "foo" + doAssert lastPathPart("/") == "" + when doslikeFileSystem: + doAssert lastPathPart(r"foo\bar.txt") == "bar.txt" + doAssert lastPathPart(r"foo\") == "foo" + + template canon(x): untyped = normalizePath(x, '/') + doAssert canon"/foo/../bar" == "/bar" + doAssert canon"foo/../bar" == "bar" + + doAssert canon"/f/../bar///" == "/bar" + doAssert canon"f/..////bar" == "bar" + + doAssert canon"../bar" == "../bar" + doAssert canon"/../bar" == "/../bar" + + doAssert canon("foo/../../bar/") == "../bar" + doAssert canon("./bla/blob/") == "bla/blob" + doAssert canon(".hiddenFile") == ".hiddenFile" + doAssert canon("./bla/../../blob/./zoo.nim") == "../blob/zoo.nim" + + doAssert canon("C:/file/to/this/long") == "C:/file/to/this/long" + doAssert canon("") == "" + doAssert canon("foobar") == "foobar" + doAssert canon("f/////////") == "f" + + doAssert relativePath("/foo/bar//baz.nim", "/foo", '/') == "bar/baz.nim" + doAssert normalizePath("./foo//bar/../baz", '/') == "foo/baz" + + doAssert relativePath("/Users/me/bar/z.nim", "/Users/other/bad", '/') == "../../me/bar/z.nim" + + doAssert relativePath("/Users/me/bar/z.nim", "/Users/other", '/') == "../me/bar/z.nim" + doAssert relativePath("/Users///me/bar//z.nim", "//Users/", '/') == "me/bar/z.nim" + doAssert relativePath("/Users/me/bar/z.nim", "/Users/me", '/') == "bar/z.nim" + doAssert relativePath("", "/users/moo", '/') == "" + doAssert relativePath("foo", "", '/') == "foo" + + doAssert joinPath("usr", "") == unixToNativePath"usr/" + doAssert joinPath("", "lib") == "lib" + doAssert joinPath("", "/lib") == unixToNativePath"/lib" + doAssert joinPath("usr/", "/lib") == unixToNativePath"usr/lib" diff --git a/tests/stdlib/tospaths.nim b/tests/stdlib/tospaths.nim deleted file mode 100644 index ce00b5a95..000000000 --- a/tests/stdlib/tospaths.nim +++ /dev/null @@ -1,99 +0,0 @@ -discard """ - output: "" -""" -# test the ospaths module - -import os, pathnorm - -doAssert unixToNativePath("") == "" -doAssert unixToNativePath(".") == $CurDir -doAssert unixToNativePath("..") == $ParDir -doAssert isAbsolute(unixToNativePath("/")) -doAssert isAbsolute(unixToNativePath("/", "a")) -doAssert isAbsolute(unixToNativePath("/a")) -doAssert isAbsolute(unixToNativePath("/a", "a")) -doAssert isAbsolute(unixToNativePath("/a/b")) -doAssert isAbsolute(unixToNativePath("/a/b", "a")) -doAssert unixToNativePath("a/b") == joinPath("a", "b") - -when defined(macos): - doAssert unixToNativePath("./") == ":" - doAssert unixToNativePath("./abc") == ":abc" - doAssert unixToNativePath("../abc") == "::abc" - doAssert unixToNativePath("../../abc") == ":::abc" - doAssert unixToNativePath("/abc", "a") == "abc" - doAssert unixToNativePath("/abc/def", "a") == "abc:def" -elif doslikeFileSystem: - doAssert unixToNativePath("./") == ".\\" - doAssert unixToNativePath("./abc") == ".\\abc" - doAssert unixToNativePath("../abc") == "..\\abc" - doAssert unixToNativePath("../../abc") == "..\\..\\abc" - doAssert unixToNativePath("/abc", "a") == "a:\\abc" - doAssert unixToNativePath("/abc/def", "a") == "a:\\abc\\def" -else: - #Tests for unix - doAssert unixToNativePath("./") == "./" - doAssert unixToNativePath("./abc") == "./abc" - doAssert unixToNativePath("../abc") == "../abc" - doAssert unixToNativePath("../../abc") == "../../abc" - doAssert unixToNativePath("/abc", "a") == "/abc" - doAssert unixToNativePath("/abc/def", "a") == "/abc/def" - -block extractFilenameTest: - doAssert extractFilename("") == "" - when defined(posix): - doAssert extractFilename("foo/bar") == "bar" - doAssert extractFilename("foo/bar.txt") == "bar.txt" - doAssert extractFilename("foo/") == "" - doAssert extractFilename("/") == "" - when doslikeFileSystem: - doAssert extractFilename(r"foo\bar") == "bar" - doAssert extractFilename(r"foo\bar.txt") == "bar.txt" - doAssert extractFilename(r"foo\") == "" - doAssert extractFilename(r"C:\") == "" - -block lastPathPartTest: - doAssert lastPathPart("") == "" - when defined(posix): - doAssert lastPathPart("foo/bar.txt") == "bar.txt" - doAssert lastPathPart("foo/") == "foo" - doAssert lastPathPart("/") == "" - when doslikeFileSystem: - doAssert lastPathPart(r"foo\bar.txt") == "bar.txt" - doAssert lastPathPart(r"foo\") == "foo" - -template canon(x): untyped = normalizePath(x, '/') -doAssert canon"/foo/../bar" == "/bar" -doAssert canon"foo/../bar" == "bar" - -doAssert canon"/f/../bar///" == "/bar" -doAssert canon"f/..////bar" == "bar" - -doAssert canon"../bar" == "../bar" -doAssert canon"/../bar" == "/../bar" - -doAssert canon("foo/../../bar/") == "../bar" -doAssert canon("./bla/blob/") == "bla/blob" -doAssert canon(".hiddenFile") == ".hiddenFile" -doAssert canon("./bla/../../blob/./zoo.nim") == "../blob/zoo.nim" - -doAssert canon("C:/file/to/this/long") == "C:/file/to/this/long" -doAssert canon("") == "" -doAssert canon("foobar") == "foobar" -doAssert canon("f/////////") == "f" - -doAssert relativePath("/foo/bar//baz.nim", "/foo", '/') == "bar/baz.nim" -doAssert normalizePath("./foo//bar/../baz", '/') == "foo/baz" - -doAssert relativePath("/Users/me/bar/z.nim", "/Users/other/bad", '/') == "../../me/bar/z.nim" - -doAssert relativePath("/Users/me/bar/z.nim", "/Users/other", '/') == "../me/bar/z.nim" -doAssert relativePath("/Users///me/bar//z.nim", "//Users/", '/') == "me/bar/z.nim" -doAssert relativePath("/Users/me/bar/z.nim", "/Users/me", '/') == "bar/z.nim" -doAssert relativePath("", "/users/moo", '/') == "" -doAssert relativePath("foo", "", '/') == "foo" - -doAssert joinPath("usr", "") == unixToNativePath"usr/" -doAssert joinPath("", "lib") == "lib" -doAssert joinPath("", "/lib") == unixToNativePath"/lib" -doAssert joinPath("usr/", "/lib") == unixToNativePath"usr/lib" -- cgit 1.4.1-2-gfad0 From 824f39b32e04e31514aade50da38516b8fadac12 Mon Sep 17 00:00:00 2001 From: Arne Döring Date: Tue, 5 Feb 2019 09:31:37 +0100 Subject: Vm bitops fixes (#10520) --- compiler/vm.nim | 5 ++++ compiler/vmdef.nim | 1 + compiler/vmgen.nim | 9 ++++++- lib/pure/bitops.nim | 27 +++++++++++-------- tests/stdlib/tbitops.nim | 67 ++++++++++++------------------------------------ 5 files changed, 47 insertions(+), 62 deletions(-) (limited to 'tests/stdlib') diff --git a/compiler/vm.nim b/compiler/vm.nim index e95a491fd..71fd2722b 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -1264,6 +1264,11 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = of opcNarrowU: decodeB(rkInt) regs[ra].intVal = regs[ra].intVal and ((1'i64 shl rb)-1) + of opcSignExtend: + # like opcNarrowS, but no out of range possible + decodeB(rkInt) + let imm = 64 - rb + regs[ra].intVal = ashr(regs[ra].intVal shl imm, imm) of opcIsNil: decodeB(rkInt) let node = regs[rb].node diff --git a/compiler/vmdef.nim b/compiler/vmdef.nim index a43f8dbba..58158a7cc 100644 --- a/compiler/vmdef.nim +++ b/compiler/vmdef.nim @@ -75,6 +75,7 @@ type opcSubStr, opcParseFloat, opcConv, opcCast, opcQuit, opcNarrowS, opcNarrowU, + opcSignExtend, opcAddStrCh, opcAddStrStr, diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index 7f3ca84ae..f513c59a7 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -986,7 +986,14 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = c.freeTemp(tmp) c.freeTemp(tmp2) - of mShlI: genBinaryABCnarrowU(c, n, dest, opcShlInt) + of mShlI: + genBinaryABC(c, n, dest, opcShlInt) + # genNarrowU modified + let t = skipTypes(n.typ, abstractVar-{tyTypeDesc}) + if t.kind in {tyUInt8..tyUInt32} or (t.kind == tyUInt and t.size < 8): + c.gABC(n, opcNarrowU, dest, TRegister(t.size*8)) + elif t.kind in {tyInt8..tyInt32} or (t.kind == tyInt and t.size < 8): + c.gABC(n, opcSignExtend, dest, TRegister(t.size*8)) of mAshrI: genBinaryABC(c, n, dest, opcAshrInt) of mBitandI: genBinaryABC(c, n, dest, opcBitandInt) of mBitorI: genBinaryABC(c, n, dest, opcBitorInt) diff --git a/lib/pure/bitops.nim b/lib/pure/bitops.nim index d258a42de..0eee3cd70 100644 --- a/lib/pure/bitops.nim +++ b/lib/pure/bitops.nim @@ -33,6 +33,18 @@ const useICC_builtins = defined(icc) and useBuiltins const useVCC_builtins = defined(vcc) and useBuiltins const arch64 = sizeof(int) == 8 +template forwardImpl(impl, arg) {.dirty.} = + when sizeof(x) <= 4: + when x is SomeSignedInt: + impl(cast[uint32](x.int32)) + else: + impl(x.uint32) + else: + when x is SomeSignedInt: + impl(cast[uint64](x.int64)) + else: + impl(x.uint64) + when defined(nimHasalignOf): import macros @@ -243,8 +255,7 @@ proc countSetBits*(x: SomeInteger): int {.inline, nosideeffect.} = # TODO: figure out if ICC support _popcnt32/_popcnt64 on platform without POPCNT. # like GCC and MSVC when nimvm: - when sizeof(x) <= 4: result = countSetBits_nim(x.uint32) - else: result = countSetBits_nim(x.uint64) + result = forwardImpl(countSetBits_nim, x) else: when useGCC_builtins: when sizeof(x) <= 4: result = builtin_popcount(x.cuint).int @@ -274,8 +285,7 @@ proc parityBits*(x: SomeInteger): int {.inline, nosideeffect.} = # Can be used a base if creating ASM version. # https://stackoverflow.com/questions/21617970/how-to-check-if-value-has-even-parity-of-bits-or-odd when nimvm: - when sizeof(x) <= 4: result = parity_impl(x.uint32) - else: result = parity_impl(x.uint64) + result = forwardImpl(parity_impl, x) else: when useGCC_builtins: when sizeof(x) <= 4: result = builtin_parity(x.uint32).int @@ -293,8 +303,7 @@ proc firstSetBit*(x: SomeInteger): int {.inline, nosideeffect.} = when noUndefined: if x == 0: return 0 - when sizeof(x) <= 4: result = firstSetBit_nim(x.uint32) - else: result = firstSetBit_nim(x.uint64) + result = forwardImpl(firstSetBit_nim, x) else: when noUndefined and not useGCC_builtins: if x == 0: @@ -328,8 +337,7 @@ proc fastLog2*(x: SomeInteger): int {.inline, nosideeffect.} = if x == 0: return -1 when nimvm: - when sizeof(x) <= 4: result = fastlog2_nim(x.uint32) - else: result = fastlog2_nim(x.uint64) + result = forwardImpl(fastlog2_nim, x) else: when useGCC_builtins: when sizeof(x) <= 4: result = 31 - builtin_clz(x.uint32).int @@ -360,8 +368,7 @@ proc countLeadingZeroBits*(x: SomeInteger): int {.inline, nosideeffect.} = if x == 0: return 0 when nimvm: - when sizeof(x) <= 4: result = sizeof(x)*8 - 1 - fastlog2_nim(x.uint32) - else: result = sizeof(x)*8 - 1 - fastlog2_nim(x.uint64) + result = sizeof(x)*8 - 1 - forwardImpl(fastlog2_nim, x) else: when useGCC_builtins: when sizeof(x) <= 4: result = builtin_clz(x.uint32).int - (32 - sizeof(x)*8) diff --git a/tests/stdlib/tbitops.nim b/tests/stdlib/tbitops.nim index b8b44703c..1cbab4870 100644 --- a/tests/stdlib/tbitops.nim +++ b/tests/stdlib/tbitops.nim @@ -1,9 +1,9 @@ discard """ + nimout: "OK" output: "OK" """ import bitops - proc main() = const U8 = 0b0011_0010'u8 const I8 = 0b0011_0010'i8 @@ -79,25 +79,6 @@ proc main() = doAssert( U8.rotateLeftBits(3) == 0b10010001'u8) doAssert( U8.rotateRightBits(3) == 0b0100_0110'u8) - static : - # test bitopts at compile time with vm - doAssert( U8.fastLog2 == 5) - doAssert( I8.fastLog2 == 5) - doAssert( U8.countLeadingZeroBits == 2) - doAssert( I8.countLeadingZeroBits == 2) - doAssert( U8.countTrailingZeroBits == 1) - doAssert( I8.countTrailingZeroBits == 1) - doAssert( U8.firstSetBit == 2) - doAssert( I8.firstSetBit == 2) - doAssert( U8.parityBits == 1) - doAssert( I8.parityBits == 1) - doAssert( U8.countSetBits == 3) - doAssert( I8.countSetBits == 3) - doAssert( U8.rotateLeftBits(3) == 0b10010001'u8) - doAssert( U8.rotateRightBits(3) == 0b0100_0110'u8) - - - template test_undefined_impl(ffunc: untyped; expected: int; is_static: bool) = doAssert( ffunc(0'u8) == expected) doAssert( ffunc(0'i8) == expected) @@ -142,26 +123,6 @@ proc main() = doAssert( U64A.rotateLeftBits(64) == U64A) doAssert( U64A.rotateRightBits(64) == U64A) - static: # check for undefined behavior with rotate by zero. - doAssert( U8.rotateLeftBits(0) == U8) - doAssert( U8.rotateRightBits(0) == U8) - doAssert( U16.rotateLeftBits(0) == U16) - doAssert( U16.rotateRightBits(0) == U16) - doAssert( U32.rotateLeftBits(0) == U32) - doAssert( U32.rotateRightBits(0) == U32) - doAssert( U64A.rotateLeftBits(0) == U64A) - doAssert( U64A.rotateRightBits(0) == U64A) - - # check for undefined behavior with rotate by integer width. - doAssert( U8.rotateLeftBits(8) == U8) - doAssert( U8.rotateRightBits(8) == U8) - doAssert( U16.rotateLeftBits(16) == U16) - doAssert( U16.rotateRightBits(16) == U16) - doAssert( U32.rotateLeftBits(32) == U32) - doAssert( U32.rotateRightBits(32) == U32) - doAssert( U64A.rotateLeftBits(64) == U64A) - doAssert( U64A.rotateRightBits(64) == U64A) - block: # mask operations var v: uint8 @@ -207,18 +168,22 @@ proc main() = var v: uint64 v.setBit(63) doAssert v == 0b1000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000'u64 - block: - # Test if RangeError is thrown if indexing out of range - try: - var v: uint32 - var i = 32 - v.setBit(i) - doAssert false - except RangeError: - discard - except: - doAssert false echo "OK" +block: # not ready for vm because exception is compile error + try: + var v: uint32 + var i = 32 + v.setBit(i) + doAssert false + except RangeError: + discard + except: + doAssert false + + main() +static: + # test everything on vm as well + main() -- cgit 1.4.1-2-gfad0 From bfb2ad507802cf91384118c208bcdce8bd07fb4b Mon Sep 17 00:00:00 2001 From: Oscar Nihlgård Date: Wed, 6 Feb 2019 20:13:29 +0100 Subject: New implementation of times.between (#10523) * Refactor ttimes * New implementation of times.between * Deprecate times.toTimeInterval --- lib/pure/times.nim | 175 ++++++++++++++++++++++++++---------------------- tests/stdlib/ttimes.nim | 173 +++++++++++++++++++++++++++++++++++++---------- 2 files changed, 232 insertions(+), 116 deletions(-) (limited to 'tests/stdlib') diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 20941fcc2..260850a0e 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -1462,97 +1462,108 @@ proc evaluateStaticInterval(interval: TimeInterval): Duration = proc between*(startDt, endDt: DateTime): TimeInterval = ## Gives the difference between ``startDt`` and ``endDt`` as a - ## ``TimeInterval``. + ## ``TimeInterval``. The following guarantees about the result is given: ## - ## **Warning:** This proc currently gives very few guarantees about the - ## result. ``a + between(a, b) == b`` is **not** true in general - ## (it's always true when UTC is used however). Neither is it guaranteed that - ## all components in the result will have the same sign. The behavior of this - ## proc might change in the future. + ## - All fields will have the same sign. + ## - If `startDt.timezone == endDt.timezone`, it is guaranteed that + ## `startDt + between(startDt, endDt) == endDt`. + ## - If `startDt.timezone != endDt.timezone`, then the result will be + ## equivalent to `between(startDt.utc, endDt.utc)`. runnableExamples: var a = initDateTime(25, mMar, 2015, 12, 0, 0, utc()) var b = initDateTime(1, mApr, 2017, 15, 0, 15, utc()) - var ti = initTimeInterval(years = 2, days = 7, hours = 3, seconds = 15) + var ti = initTimeInterval(years = 2, weeks = 1, hours = 3, seconds = 15) doAssert between(a, b) == ti doAssert between(a, b) == -between(b, a) - var startDt = startDt.utc() - var endDt = endDt.utc() - - if endDt == startDt: - return initTimeInterval() + if startDt.timezone != endDt.timezone: + return between(startDt.utc, endDt.utc) elif endDt < startDt: return -between(endDt, startDt) - var coeffs: array[FixedTimeUnit, int64] = unitWeights - var timeParts: array[FixedTimeUnit, int] - for unit in Nanoseconds..Weeks: - timeParts[unit] = 0 - - for unit in Seconds..Days: - coeffs[unit] = coeffs[unit] div unitWeights[Seconds] - - var startTimepart = initTime( - nanosecond = startDt.nanosecond, - unix = startDt.hour * coeffs[Hours] + startDt.minute * coeffs[Minutes] + - startDt.second - ) - var endTimepart = initTime( - nanosecond = endDt.nanosecond, - unix = endDt.hour * coeffs[Hours] + endDt.minute * coeffs[Minutes] + - endDt.second - ) - # We wand timeParts for Seconds..Hours be positive, so we'll borrow one day - if endTimepart < startTimepart: - timeParts[Days] = -1 - - let diffTime = endTimepart - startTimepart - timeParts[Seconds] = diffTime.seconds.int() - #Nanoseconds - preliminary count - timeParts[Nanoseconds] = diffTime.nanoseconds - for unit in countdown(Milliseconds, Microseconds): - timeParts[unit] += timeParts[Nanoseconds] div coeffs[unit].int() - timeParts[Nanoseconds] -= timeParts[unit] * coeffs[unit].int() - - #Counting Seconds .. Hours - final, Days - preliminary - for unit in countdown(Days, Minutes): - timeParts[unit] += timeParts[Seconds] div coeffs[unit].int() - # Here is accounted the borrowed day - timeParts[Seconds] -= timeParts[unit] * coeffs[unit].int() - - # Set Nanoseconds .. Hours in result - result.nanoseconds = timeParts[Nanoseconds] - result.microseconds = timeParts[Microseconds] - result.milliseconds = timeParts[Milliseconds] - result.seconds = timeParts[Seconds] - result.minutes = timeParts[Minutes] - result.hours = timeParts[Hours] - - #Days - if endDt.monthday.int + timeParts[Days] < startDt.monthday.int(): - if endDt.month > 1.Month: - endDt.month -= 1.Month + type Date = tuple[year, month, monthday: int] + var startDate: Date = (startDt.year, startDt.month.ord, startDt.monthday) + var endDate: Date = (endDt.year, endDt.month.ord, endDt.monthday) + + # Subtract one day from endDate if time of day is earlier than startDay + # The subtracted day will be counted by fixed units (hour and lower) + # at the end of this proc + if (endDt.hour, endDt.minute, endDt.second, endDt.nanosecond) < + (startDt.hour, startDt.minute, startDt.second, startDt.nanosecond): + if endDate.month == 1 and endDate.monthday == 1: + endDate.year.dec + endDate.monthday = 31 + endDate.month = 12 + elif endDate.monthday == 1: + endDate.month.dec + endDate.monthday = getDaysInMonth(endDate.month.Month, endDate.year) else: - endDt.month = 12.Month - endDt.year -= 1 - timeParts[Days] += endDt.monthday.int() + getDaysInMonth( - endDt.month, endDt.year) - startDt.monthday.int() - else: - timeParts[Days] += endDt.monthday.int() - - startDt.monthday.int() - - result.days = timeParts[Days] - - #Months - if endDt.month < startDt.month: - result.months = endDt.month.int() + 12 - startDt.month.int() - endDt.year -= 1 - else: - result.months = endDt.month.int() - - startDt.month.int() + endDate.monthday.dec # Years - result.years = endDt.year - startDt.year + result.years.inc endDate.year - startDate.year - 1 + if (startDate.month, startDate.monthday) <= (endDate.month, endDate.monthday): + result.years.inc + startDate.year.inc result.years + + # Months + if startDate.year < endDate.year: + result.months.inc 12 - startDate.month # Move to dec + if endDate.month != 1 or (startDate.monthday <= endDate.monthday): + result.months.inc + startDate.year = endDate.year + startDate.month = 1 + else: + startDate.month = 12 + if startDate.year == endDate.year: + if (startDate.monthday <= endDate.monthday): + result.months.inc endDate.month - startDate.month + startDate.month = endDate.month + elif endDate.month != 1: + let month = endDate.month - 1 + let daysInMonth = getDaysInMonth(month.Month, startDate.year) + if daysInMonth < startDate.monthday: + if startDate.monthday - daysInMonth < endDate.monthday: + result.months.inc endDate.month - startDate.month - 1 + startDate.month = endDate.month + startDate.monthday = startDate.monthday - daysInMonth + else: + result.months.inc endDate.month - startDate.month - 2 + startDate.month = endDate.month - 2 + else: + result.months.inc endDate.month - startDate.month - 1 + startDate.month = endDate.month - 1 + + # Days + # This means that start = dec and end = jan + if startDate.year < endDate.year: + result.days.inc 31 - startDate.monthday + endDate.monthday + startDate = endDate + else: + while startDate.month < endDate.month: + let daysInMonth = getDaysInMonth(startDate.month.Month, startDate.year) + result.days.inc daysInMonth - startDate.monthday + 1 + startDate.month.inc + startDate.monthday = 1 + result.days.inc endDate.monthday - startDate.monthday + result.weeks = result.days div 7 + result.days = result.days mod 7 + startDate = endDate + + # Handle hours, minutes, seconds, milliseconds, microseconds and nanoseconds + let newStartDt = initDateTime(startDate.monthday, startDate.month.Month, + startDate.year, startDt.hour, startDt.minute, startDt.second, + startDt.nanosecond, startDt.timezone) + let dur = endDt - newStartDt + let parts = toParts(dur) + # There can still be a full day in `parts` since `Duration` and `TimeInterval` + # models days differently. + result.hours = parts[Hours].int + parts[Days].int * 24 + result.minutes = parts[Minutes].int + result.seconds = parts[Seconds].int + result.milliseconds = parts[Milliseconds].int + result.microseconds = parts[Microseconds].int + result.nanoseconds = parts[Nanoseconds].int proc `+`*(time: Time, interval: TimeInterval): Time = ## Adds `interval` to `time`. @@ -2405,10 +2416,12 @@ proc countYearsAndDays*(daySpan: int): tuple[years: int, days: int] result.years = days div 365 result.days = days mod 365 -proc toTimeInterval*(time: Time): TimeInterval = - ## Converts a Time to a TimeInterval. +proc toTimeInterval*(time: Time): TimeInterval + {.deprecated: "Use `between` instead".} = + ## Converts a Time to a TimeInterval. To be used when diffing times. ## - ## To be used when diffing times. Consider using `between` instead. + ## **Deprecated since version 0.20.0:** Use the `between proc + ## <#between,DateTime,DateTime>`_ instead. runnableExamples: let a = fromUnix(10) let b = fromUnix(1_500_000_000) diff --git a/tests/stdlib/ttimes.nim b/tests/stdlib/ttimes.nim index 3999c968f..456ff6315 100644 --- a/tests/stdlib/ttimes.nim +++ b/tests/stdlib/ttimes.nim @@ -115,6 +115,13 @@ template runTimezoneTests() = check toTime(parsedJan).toUnix == 1451962800 check toTime(parsedJul).toUnix == 1467342000 +template usingTimezone(tz: string, body: untyped) = + when defined(linux) or defined(macosx): + let oldZone = getEnv("TZ") + putEnv("TZ", tz) + body + putEnv("TZ", oldZone) + suite "ttimes": # Generate tests for multiple timezone files where available @@ -123,37 +130,47 @@ suite "ttimes": let tz_dir = getEnv("TZDIR", "/usr/share/zoneinfo") const f = "yyyy-MM-dd HH:mm zzz" - let orig_tz = getEnv("TZ") var tz_cnt = 0 - for tz_fn in walkFiles(tz_dir & "/**/*"): - if symlinkExists(tz_fn) or tz_fn.endsWith(".tab") or - tz_fn.endsWith(".list"): + for timezone in walkFiles(tz_dir & "/**/*"): + if symlinkExists(timezone) or timezone.endsWith(".tab") or + timezone.endsWith(".list"): continue - test "test for " & tz_fn: - tz_cnt.inc - putEnv("TZ", tz_fn) - runTimezoneTests() + usingTimezone(timezone): + test "test for " & timezone: + tz_cnt.inc + runTimezoneTests() test "enough timezone files tested": check tz_cnt > 10 - test "dst handling": - putEnv("TZ", "Europe/Stockholm") - # In case of an impossible time, the time is moved to after the impossible time period - check initDateTime(26, mMar, 2017, 02, 30, 00).format(f) == "2017-03-26 03:30 +02:00" + else: + # not on Linux or macosx: run in the local timezone only + test "parseTest": + runTimezoneTests() + + test "dst handling": + usingTimezone("Europe/Stockholm"): + # In case of an impossible time, the time is moved to after the + # impossible time period + check initDateTime(26, mMar, 2017, 02, 30, 00).format(f) == + "2017-03-26 03:30 +02:00" # In case of an ambiguous time, the earlier time is choosen - check initDateTime(29, mOct, 2017, 02, 00, 00).format(f) == "2017-10-29 02:00 +02:00" + check initDateTime(29, mOct, 2017, 02, 00, 00).format(f) == + "2017-10-29 02:00 +02:00" # These are just dates on either side of the dst switch - check initDateTime(29, mOct, 2017, 01, 00, 00).format(f) == "2017-10-29 01:00 +02:00" + check initDateTime(29, mOct, 2017, 01, 00, 00).format(f) == + "2017-10-29 01:00 +02:00" check initDateTime(29, mOct, 2017, 01, 00, 00).isDst - check initDateTime(29, mOct, 2017, 03, 01, 00).format(f) == "2017-10-29 03:01 +01:00" + check initDateTime(29, mOct, 2017, 03, 01, 00).format(f) == + "2017-10-29 03:01 +01:00" check (not initDateTime(29, mOct, 2017, 03, 01, 00).isDst) - check initDateTime(21, mOct, 2017, 01, 00, 00).format(f) == "2017-10-21 01:00 +02:00" + check initDateTime(21, mOct, 2017, 01, 00, 00).format(f) == + "2017-10-21 01:00 +02:00" - test "issue #6520": - putEnv("TZ", "Europe/Stockholm") + test "issue #6520": + usingTimezone("Europe/Stockholm"): var local = fromUnix(1469275200).local var utc = fromUnix(1469275200).utc @@ -161,35 +178,28 @@ suite "ttimes": local.utcOffset = 0 check claimedOffset == utc.toTime - local.toTime - test "issue #5704": - putEnv("TZ", "Asia/Seoul") - let diff = parse("19700101-000000", "yyyyMMdd-hhmmss").toTime - parse("19000101-000000", "yyyyMMdd-hhmmss").toTime + test "issue #5704": + usingTimezone("Asia/Seoul"): + let diff = parse("19700101-000000", "yyyyMMdd-hhmmss").toTime - + parse("19000101-000000", "yyyyMMdd-hhmmss").toTime check diff == initDuration(seconds = 2208986872) - test "issue #6465": - putEnv("TZ", "Europe/Stockholm") + test "issue #6465": + usingTimezone("Europe/Stockholm"): let dt = parse("2017-03-25 12:00", "yyyy-MM-dd hh:mm") check $(dt + initTimeInterval(days = 1)) == "2017-03-26T12:00:00+02:00" check $(dt + initDuration(days = 1)) == "2017-03-26T13:00:00+02:00" - test "datetime before epoch": - check $fromUnix(-2147483648).utc == "1901-12-13T20:45:52Z" - - test "adding/subtracting time across dst": - putenv("TZ", "Europe/Stockholm") - + test "adding/subtracting time across dst": + usingTimezone("Europe/Stockholm"): let dt1 = initDateTime(26, mMar, 2017, 03, 00, 00) check $(dt1 - 1.seconds) == "2017-03-26T01:59:59+01:00" var dt2 = initDateTime(29, mOct, 2017, 02, 59, 59) check $(dt2 + 1.seconds) == "2017-10-29T02:00:00+01:00" - putEnv("TZ", orig_tz) - - else: - # not on Linux or macosx: run in the local timezone only - test "parseTest": - runTimezoneTests() + test "datetime before epoch": + check $fromUnix(-2147483648).utc == "1901-12-13T20:45:52Z" test "incorrect inputs: empty string": parseTestExcp("", "yyyy-MM-dd") @@ -485,3 +495,96 @@ suite "ttimes": check getDayOfWeek(21, mSep, 1970) == dMon check getDayOfWeek(01, mJan, 2000) == dSat check getDayOfWeek(01, mJan, 2021) == dFri + + test "between - simple": + let x = initDateTime(10, mJan, 2018, 13, 00, 00) + let y = initDateTime(11, mJan, 2018, 12, 00, 00) + doAssert x + between(x, y) == y + + test "between - dst start": + usingTimezone("Europe/Stockholm"): + let x = initDateTime(25, mMar, 2018, 00, 00, 00) + let y = initDateTime(25, mMar, 2018, 04, 00, 00) + doAssert x + between(x, y) == y + + test "between - empty interval": + let x = now() + let y = x + doAssert x + between(x, y) == y + + test "between - dst end": + usingTimezone("Europe/Stockholm"): + let x = initDateTime(27, mOct, 2018, 02, 00, 00) + let y = initDateTime(28, mOct, 2018, 01, 00, 00) + doAssert x + between(x, y) == y + + test "between - long day": + usingTimezone("Europe/Stockholm"): + # This day is 25 hours long in Europe/Stockholm + let x = initDateTime(28, mOct, 2018, 00, 30, 00) + let y = initDateTime(29, mOct, 2018, 00, 00, 00) + doAssert between(x, y) == 24.hours + 30.minutes + doAssert x + between(x, y) == y + + test "between - offset change edge case": + # This test case is important because in this case + # `x + between(x.utc, y.utc) == y` is not true, which is very rare. + usingTimezone("America/Belem"): + let x = initDateTime(24, mOct, 1987, 00, 00, 00) + let y = initDateTime(26, mOct, 1987, 23, 00, 00) + doAssert x + between(x, y) == y + doAssert y + between(y, x) == x + + test "between - all units": + let x = initDateTime(1, mJan, 2000, 00, 00, 00, utc()) + let ti = initTimeInterval(1, 1, 1, 1, 1, 1, 1, 1, 1, 1) + let y = x + ti + doAssert between(x, y) == ti + doAssert between(y, x) == -ti + + test "between - monthday overflow": + let x = initDateTime(31, mJan, 2001, 00, 00, 00, utc()) + let y = initDateTime(1, mMar, 2001, 00, 00, 00, utc()) + doAssert x + between(x, y) == y + + test "between - misc": + block: + let x = initDateTime(31, mDec, 2000, 12, 00, 00, utc()) + let y = initDateTime(01, mJan, 2001, 00, 00, 00, utc()) + doAssert between(x, y) == 12.hours + + block: + let x = initDateTime(31, mDec, 2000, 12, 00, 00, utc()) + let y = initDateTime(02, mJan, 2001, 00, 00, 00, utc()) + doAssert between(x, y) == 1.days + 12.hours + + block: + let x = initDateTime(31, mDec, 1995, 00, 00, 00, utc()) + let y = initDateTime(01, mFeb, 2000, 00, 00, 00, utc()) + doAssert x + between(x, y) == y + + block: + let x = initDateTime(01, mDec, 1995, 00, 00, 00, utc()) + let y = initDateTime(31, mJan, 2000, 00, 00, 00, utc()) + doAssert x + between(x, y) == y + + block: + let x = initDateTime(31, mJan, 2000, 00, 00, 00, utc()) + let y = initDateTime(01, mFeb, 2000, 00, 00, 00, utc()) + doAssert x + between(x, y) == y + + block: + let x = initDateTime(01, mJan, 1995, 12, 00, 00, utc()) + let y = initDateTime(01, mFeb, 1995, 00, 00, 00, utc()) + doAssert between(x, y) == 4.weeks + 2.days + 12.hours + + block: + let x = initDateTime(31, mJan, 1995, 00, 00, 00, utc()) + let y = initDateTime(10, mFeb, 1995, 00, 00, 00, utc()) + doAssert x + between(x, y) == y + + block: + let x = initDateTime(31, mJan, 1995, 00, 00, 00, utc()) + let y = initDateTime(10, mMar, 1995, 00, 00, 00, utc()) + doAssert x + between(x, y) == y + doAssert between(x, y) == 1.months + 1.weeks -- cgit 1.4.1-2-gfad0