diff options
author | Andreas Rumpf <rumpf_a@web.de> | 2019-08-08 08:41:05 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-08-08 08:41:05 +0200 |
commit | c8cffaf42037ae8defe59d9a1fb7d202655aa1ee (patch) | |
tree | d9bf11748ee18dccf00fefb2f3d6aaeff8e115ff | |
parent | c0d240b8cd3dc08d25c671b0dc7614fbfa980c2e (diff) | |
download | Nim-c8cffaf42037ae8defe59d9a1fb7d202655aa1ee.tar.gz |
Incremental compilation (IC): Improvements (#11881)
* IC: C codegen is aware of IC * manual: minor change to make VSCode's RST plugin render it properly * IC: minor refactoring * testament: code refactorings * rodutils: removed dead code * IC: always build the compiler with the IC feature * IC: C codegen improvements * IC: implement the undocumented -d:nimMustCache option for testing purposes * IC: added first basic tests * IC: extensive testing of the deserialization feature * testament: refactoring; better IC tests * IC: removes 'nimMustCache' flag; readonly does the same * testament: minor refactoring * update Nimble version * testament: removed dead code and imports; IC: added simple test * IC: progress
-rw-r--r-- | compiler/cgen.nim | 115 | ||||
-rw-r--r-- | compiler/commands.nim | 4 | ||||
-rw-r--r-- | compiler/incremental.nim | 5 | ||||
-rw-r--r-- | compiler/modules.nim | 2 | ||||
-rw-r--r-- | compiler/passes.nim | 6 | ||||
-rw-r--r-- | compiler/rodimpl.nim | 21 | ||||
-rw-r--r-- | compiler/rodutils.nim | 11 | ||||
-rw-r--r-- | doc/manual.rst | 4 | ||||
-rw-r--r-- | koch.nim | 2 | ||||
-rw-r--r-- | testament/categories.nim | 161 | ||||
-rw-r--r-- | testament/htmlgen.nim | 2 | ||||
-rw-r--r-- | testament/important_packages.nim | 1 | ||||
-rw-r--r-- | testament/specs.nim | 5 | ||||
-rw-r--r-- | testament/tester.nim | 195 | ||||
-rw-r--r-- | tests/ic/tgenerics.nim | 39 | ||||
-rw-r--r-- | tests/ic/thallo.nim | 28 |
16 files changed, 329 insertions, 272 deletions
diff --git a/compiler/cgen.nim b/compiler/cgen.nim index c8906f380..c0ef4e1e3 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -1108,7 +1108,8 @@ proc genProcNoForward(m: BModule, prc: PSym) = # a check for ``m.declaredThings``. if not containsOrIncl(m.declaredThings, prc.id): #if prc.loc.k == locNone: - # mangle the inline proc based on the module where it is defined - not on the first module that uses it + # mangle the inline proc based on the module where it is defined - + # not on the first module that uses it fillProcLoc(findPendingModule(m, prc), prc.ast[namePos]) #elif {sfExportc, sfImportc} * prc.flags == {}: # # reset name to restore consistency in case of hashing collisions: @@ -1781,10 +1782,6 @@ proc rawNewModule(g: BModuleList; module: PSym, filename: AbsoluteFile): BModule else: AbsoluteFile"" open(result.ndi, ndiName, g.config) -proc nullify[T](arr: var T) = - for i in low(arr)..high(arr): - arr[i] = Rope(nil) - proc rawNewModule(g: BModuleList; module: PSym; conf: ConfigRef): BModule = result = rawNewModule(g, module, AbsoluteFile toFullPath(conf, module.position.FileIndex)) @@ -1875,7 +1872,9 @@ proc myProcess(b: PPassContext, n: PNode): PNode = result = n if b == nil: return var m = BModule(b) - if passes.skipCodegen(m.config, n): return + if passes.skipCodegen(m.config, n) or + not moduleHasChanged(m.g.graph, m.module): + return m.initProc.options = initProcOptions(m) #softRnl = if optLineDir in m.config.options: noRnl else: rnl # XXX replicate this logic! @@ -1886,9 +1885,10 @@ proc myProcess(b: PPassContext, n: PNode): PNode = genStmts(m.initProc, transformedN) proc shouldRecompile(m: BModule; code: Rope, cfile: Cfile): bool = - result = true if optForceFullMake notin m.config.globalOptions: - if not equalsFile(code, cfile.cname): + if not moduleHasChanged(m.g.graph, m.module): + result = false + elif not equalsFile(code, cfile.cname): if false: #m.config.symbolFiles == readOnlySf: #isDefined(m.config, "nimdiff"): if fileExists(cfile.cname): @@ -1898,12 +1898,15 @@ proc shouldRecompile(m: BModule; code: Rope, cfile: Cfile): bool = echo "new file ", cfile.cname.string if not writeRope(code, cfile.cname): rawMessage(m.config, errCannotOpenFile, cfile.cname.string) - return - if fileExists(cfile.obj) and os.fileNewer(cfile.obj.string, cfile.cname.string): + result = true + elif fileExists(cfile.obj) and os.fileNewer(cfile.obj.string, cfile.cname.string): result = false + else: + result = true else: if not writeRope(code, cfile.cname): rawMessage(m.config, errCannotOpenFile, cfile.cname.string) + result = true # We need 2 different logics here: pending modules (including # 'nim__dat') may require file merging for the combination of dead code @@ -1915,18 +1918,19 @@ proc writeModule(m: BModule, pending: bool) = let cfile = getCFile(m) if true or optForceFullMake in m.config.globalOptions: - genInitCode(m) - finishTypeDescriptions(m) - if sfMainModule in m.module.flags: - # generate main file: - genMainProc(m) - add(m.s[cfsProcHeaders], m.g.mainModProcs) - generateThreadVarsSize(m) + if moduleHasChanged(m.g.graph, m.module): + genInitCode(m) + finishTypeDescriptions(m) + if sfMainModule in m.module.flags: + # generate main file: + genMainProc(m) + add(m.s[cfsProcHeaders], m.g.mainModProcs) + generateThreadVarsSize(m) var cf = Cfile(nimname: m.module.name.s, cname: cfile, obj: completeCfilePath(m.config, toObjFile(m.config, cfile)), flags: {}) var code = genModule(m, cf) - if code != nil: + if code != nil or m.config.symbolFiles != disabledSf: when hasTinyCBackend: if conf.cmd == cmdRun: tccgen.compileCCode($code) @@ -1983,46 +1987,47 @@ proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode = for destructorCall in graph.globalDestructors: n.add destructorCall if passes.skipCodegen(m.config, n): return - # if the module is cached, we don't regenerate the main proc - # nor the dispatchers? But if the dispatchers changed? - # XXX emit the dispatchers into its own .c file? - if n != nil: - m.initProc.options = initProcOptions(m) - genStmts(m.initProc, n) + if moduleHasChanged(graph, m.module): + # if the module is cached, we don't regenerate the main proc + # nor the dispatchers? But if the dispatchers changed? + # XXX emit the dispatchers into its own .c file? + if n != nil: + m.initProc.options = initProcOptions(m) + genStmts(m.initProc, n) - if m.hcrOn: - # make sure this is pulled in (meaning hcrGetGlobal() is called for it during init) - discard cgsym(m, "programResult") - if m.inHcrInitGuard: - endBlock(m.initProc) - - if sfMainModule in m.module.flags: if m.hcrOn: - # pull ("define" since they are inline when HCR is on) these functions in the main file - # so it can load the HCR runtime and later pass the library handle to the HCR runtime which - # will in turn pass it to the other modules it initializes so they can initialize the - # register/get procs so they don't have to have the definitions of these functions as well - discard cgsym(m, "nimLoadLibrary") - discard cgsym(m, "nimLoadLibraryError") - discard cgsym(m, "nimGetProcAddr") - discard cgsym(m, "procAddrError") - discard cgsym(m, "rawWrite") - - # raise dependencies on behalf of genMainProc - if m.config.target.targetOS != osStandalone and m.config.selectedGC != gcNone: - discard cgsym(m, "initStackBottomWith") - if emulatedThreadVars(m.config) and m.config.target.targetOS != osStandalone: - discard cgsym(m, "initThreadVarsEmulation") + # make sure this is pulled in (meaning hcrGetGlobal() is called for it during init) + discard cgsym(m, "programResult") + if m.inHcrInitGuard: + endBlock(m.initProc) - if m.g.breakpoints != nil: - discard cgsym(m, "dbgRegisterBreakpoint") - if optEndb in m.config.options: - discard cgsym(m, "dbgRegisterFilename") - - if m.g.forwardedProcs.len == 0: - incl m.flags, objHasKidsValid - let disp = generateMethodDispatchers(graph) - for x in disp: genProcAux(m, x.sym) + if sfMainModule in m.module.flags: + if m.hcrOn: + # pull ("define" since they are inline when HCR is on) these functions in the main file + # so it can load the HCR runtime and later pass the library handle to the HCR runtime which + # will in turn pass it to the other modules it initializes so they can initialize the + # register/get procs so they don't have to have the definitions of these functions as well + discard cgsym(m, "nimLoadLibrary") + discard cgsym(m, "nimLoadLibraryError") + discard cgsym(m, "nimGetProcAddr") + discard cgsym(m, "procAddrError") + discard cgsym(m, "rawWrite") + + # raise dependencies on behalf of genMainProc + if m.config.target.targetOS != osStandalone and m.config.selectedGC != gcNone: + discard cgsym(m, "initStackBottomWith") + if emulatedThreadVars(m.config) and m.config.target.targetOS != osStandalone: + discard cgsym(m, "initThreadVarsEmulation") + + if m.g.breakpoints != nil: + discard cgsym(m, "dbgRegisterBreakpoint") + if optEndb in m.config.options: + discard cgsym(m, "dbgRegisterFilename") + + if m.g.forwardedProcs.len == 0: + incl m.flags, objHasKidsValid + let disp = generateMethodDispatchers(graph) + for x in disp: genProcAux(m, x.sym) m.g.modulesClosed.add m diff --git a/compiler/commands.nim b/compiler/commands.nim index ed0320a7c..87ded61db 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -30,6 +30,8 @@ import wordrecg, parseutils, nimblecmd, parseopt, sequtils, lineinfos, pathutils, strtabs +from incremental import nimIncremental + # but some have deps to imported modules. Yay. bootSwitch(usedTinyC, hasTinyCBackend, "-d:tinyc") bootSwitch(usedNativeStacktrace, @@ -669,7 +671,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; helpOnError(conf, pass) of "symbolfiles": discard "ignore for backwards compat" of "incremental": - when not defined(nimIncremental): + when not nimIncremental: localError(conf, info, "the compiler was not built with " & "incremental compilation features; bootstrap with " & "-d:nimIncremental to enable") diff --git a/compiler/incremental.nim b/compiler/incremental.nim index 29528bbd3..6cc085020 100644 --- a/compiler/incremental.nim +++ b/compiler/incremental.nim @@ -10,13 +10,14 @@ ## Basic type definitions the module graph needs in order to support ## incremental compilations. -const nimIncremental* = defined(nimIncremental) +const nimIncremental* = true # defined(nimIncremental) import options, lineinfos when nimIncremental: import ast, msgs, intsets, btrees, db_sqlite, std / sha1, pathutils from strutils import parseInt + from os import isAbsolute type Writer* = object @@ -47,7 +48,7 @@ when nimIncremental: proc hashFileCached*(conf: ConfigRef; fileIdx: FileIndex; fullpath: AbsoluteFile): string = result = msgs.getHash(conf, fileIdx) - if result.len == 0: + if result.len == 0 and isAbsolute(string fullpath): result = $secureHashFile(string fullpath) msgs.setHash(conf, fileIdx, result) diff --git a/compiler/modules.nim b/compiler/modules.nim index 40d9a904c..13845e6e9 100644 --- a/compiler/modules.nim +++ b/compiler/modules.nim @@ -77,8 +77,6 @@ proc compileModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymFlags): P if result == nil: result = newModule(graph, fileIdx) result.flags = result.flags + flags - if sfMainModule in result.flags: - graph.config.mainPackageId = result.owner.id result.id = id registerModule(graph, result) else: diff --git a/compiler/passes.nim b/compiler/passes.nim index e917c1ce4..d6bbd4d14 100644 --- a/compiler/passes.nim +++ b/compiler/passes.nim @@ -113,6 +113,8 @@ const nkExportStmt, nkExportExceptStmt, nkFromStmt, nkImportStmt, nkImportExceptStmt} proc prepareConfigNotes(graph: ModuleGraph; module: PSym) = + if sfMainModule in module.flags: + graph.config.mainPackageId = module.owner.id # don't be verbose unless the module belongs to the main package: if module.owner.id == graph.config.mainPackageId: graph.config.notes = graph.config.mainPackageNotes @@ -121,7 +123,7 @@ proc prepareConfigNotes(graph: ModuleGraph; module: PSym) = graph.config.notes = graph.config.foreignPackageNotes proc moduleHasChanged*(graph: ModuleGraph; module: PSym): bool {.inline.} = - result = module.id >= 0 + result = module.id >= 0 or isDefined(graph.config, "nimBackendAssumesChange") proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream): bool {.discardable.} = if graph.stopCompile(): return true @@ -131,7 +133,7 @@ proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream): bool { s: PLLStream fileIdx = module.fileIdx prepareConfigNotes(graph, module) - if not moduleHasChanged(graph, module): + if module.id < 0: # new module caching mechanism: for i in 0 ..< graph.passes.len: if not isNil(graph.passes[i].open) and not graph.passes[i].isFrontend: diff --git a/compiler/rodimpl.nim b/compiler/rodimpl.nim index f5f689939..69b50c391 100644 --- a/compiler/rodimpl.nim +++ b/compiler/rodimpl.nim @@ -17,7 +17,6 @@ import strutils, os, intsets, tables, ropes, db_sqlite, msgs, options, types, ## - Dependency computation should use *signature* hashes in order to ## avoid recompiling dependent modules. ## - Patch the rest of the compiler to do lazy loading of proc bodies. -## - Patch the C codegen to cache proc bodies and maybe types. template db(): DbConn = g.incr.db @@ -72,9 +71,12 @@ proc getModuleId(g: ModuleGraph; fileIdx: FileIndex; fullpath: AbsoluteFile): in # not changed, so use the cached AST: doAssert(result != 0) var cycleCheck = initIntSet() - if not needsRecompile(g, fileIdx, fullpath, cycleCheck) and not g.incr.configChanged: - echo "cached successfully! ", string fullpath - return -result + if not needsRecompile(g, fileIdx, fullpath, cycleCheck): + if not g.incr.configChanged or g.config.symbolFiles == readOnlySf: + #echo "cached successfully! ", string fullpath + return -result + elif g.config.symbolFiles == readOnlySf: + internalError(g.config, "file needs to be recompiled: " & (string fullpath)) db.exec(sql"update modules set fullHash = ? where id = ?", currentFullhash, module[0]) db.exec(sql"delete from deps where module = ?", module[0]) db.exec(sql"delete from types where module = ?", module[0]) @@ -296,7 +298,7 @@ proc encodeSym(g: ModuleGraph, s: PSym, result: var string) = pushSym(w, s.owner) if s.flags != {}: result.add('$') - encodeVInt(cast[int32](s.flags), result) + encodeVBiggestInt(cast[int64](s.flags), result) if s.magic != mNone: result.add('@') encodeVInt(ord(s.magic), result) @@ -723,7 +725,7 @@ proc loadSymFromBlob(g; b; info: TLineInfo): PSym = result.owner = loadSym(g, decodeVInt(b.s, b.pos), result.info) if b.s[b.pos] == '$': inc(b.pos) - result.flags = cast[TSymFlags](int32(decodeVInt(b.s, b.pos))) + result.flags = cast[TSymFlags](decodeVBiggestInt(b.s, b.pos)) if b.s[b.pos] == '@': inc(b.pos) result.magic = TMagic(decodeVInt(b.s, b.pos)) @@ -756,6 +758,7 @@ proc loadSymFromBlob(g; b; info: TLineInfo): PSym = if b.s[b.pos] == '\24': inc b.pos result.transformedBody = decodeNode(g, b, result.info) + #result.transformedBody = nil of skModule, skPackage: decodeInstantiations(g, b, result.info, result.usedGenerics) of skLet, skVar, skField, skForVar: @@ -769,7 +772,7 @@ proc loadSymFromBlob(g; b; info: TLineInfo): PSym = if b.s[b.pos] == '(': #if result.kind in routineKinds: - # result.ast = decodeNodeLazyBody(b, result.info, result) + # result.ast = nil #else: result.ast = decodeNode(g, b, result.info) if sfCompilerProc in result.flags: @@ -886,6 +889,10 @@ proc replay(g: ModuleGraph; module: PSym; n: PNode) = internalAssert g.config, imported.id < 0 of nkStmtList, nkStmtListExpr: for x in n: replay(g, module, x) + of nkExportStmt: + for x in n: + doAssert x.kind == nkSym + strTableAdd(module.tab, x.sym) else: discard "nothing to do for this node" proc loadNode*(g: ModuleGraph; module: PSym): PNode = diff --git a/compiler/rodutils.nim b/compiler/rodutils.nim index 43fb42de9..17c4832bf 100644 --- a/compiler/rodutils.nim +++ b/compiler/rodutils.nim @@ -46,14 +46,9 @@ proc toStrMaxPrecision*(f: BiggestFloat, literalPostfix = ""): string = of fcNegInf: result = "-INF" else: - when defined(nimNoArrayToCstringConversion): - result = newString(81) - let n = c_snprintf(result.cstring, result.len.uint, "%#.16e%s", f, literalPostfix.cstring) - setLen(result, n) - else: - var buf: array[0..80, char] - discard c_snprintf(buf.cstring, buf.len.uint, "%#.16e%s", f, literalPostfix.cstring) - result = $buf.cstring + result = newString(81) + let n = c_snprintf(result.cstring, result.len.uint, "%#.16e%s", f, literalPostfix.cstring) + setLen(result, n) proc encodeStr*(s: string, result: var string) = for i in 0 ..< len(s): diff --git a/doc/manual.rst b/doc/manual.rst index 61388a9cb..61a73357d 100644 --- a/doc/manual.rst +++ b/doc/manual.rst @@ -95,8 +95,8 @@ and code execution in the executable. The compiler parses Nim source code into an internal data structure called the `abstract syntax tree`:idx: (`AST`:idx:). Then, before executing the code or -compiling it into the executable, it transforms the AST through `semantic -analysis`:idx:. This adds semantic information such as expression types, +compiling it into the executable, it transforms the AST through +`semantic analysis`:idx:. This adds semantic information such as expression types, identifier meanings, and in some cases expression values. An error detected during semantic analysis is called a `static error`:idx:. Errors described in this manual are static errors when not otherwise specified. diff --git a/koch.nim b/koch.nim index 8845ba85e..57c490449 100644 --- a/koch.nim +++ b/koch.nim @@ -10,7 +10,7 @@ # const - NimbleStableCommit = "d15c8530cb7480ce39ffa85a2dd9819d2d4fc645" # 0.10.2 + NimbleStableCommit = "da82e3111e662fc1b12f96b3cddd66c749c0f686" # master when defined(gcc) and defined(windows): when defined(x86): diff --git a/testament/categories.nim b/testament/categories.nim index 3db90d15f..745abebe3 100644 --- a/testament/categories.nim +++ b/testament/categories.nim @@ -10,8 +10,9 @@ ## Include for the tester that contains test suites that test special features ## of the compiler. +# included from tester.nim + import important_packages -import sequtils const specialCategories = [ @@ -24,6 +25,7 @@ const "gc", "io", "js", + "ic", "lib", "longgc", "manyloc", @@ -41,58 +43,52 @@ const "dir with space" ] -# included from tester.nim -# ---------------- ROD file tests --------------------------------------------- +proc isTestFile*(file: string): bool = + let (_, name, ext) = splitFile(file) + result = ext == ".nim" and name.startsWith("t") -const - rodfilesDir = "tests/rodfiles" - -proc delNimCache(filename, options: string) = - for target in low(TTarget)..high(TTarget): - let dir = nimcacheDir(filename, options, target) - try: - removeDir(dir) - except OSError: - echo "[Warning] could not delete: ", dir - -proc runRodFiles(r: var TResults, cat: Category, options: string) = - template test(filename: string, clearCacheFirst=false) = - if clearCacheFirst: delNimCache(filename, options) - testSpec r, makeTest(rodfilesDir / filename, options, cat) - - - # test basic recompilation scheme: - test "hallo", true - test "hallo" - when false: - # test incremental type information: - test "hallo2" - - # test type converters: - test "aconv", true - test "bconv" - - # test G, A, B example from the documentation; test init sections: - test "deada", true - test "deada2" - - when false: - # test method generation: - test "bmethods", true - test "bmethods2" - - # test generics: - test "tgeneric1", true - test "tgeneric2" - -proc compileRodFiles(r: var TResults, cat: Category, options: string) = - template test(filename: untyped, clearCacheFirst=true) = - if clearCacheFirst: delNimCache(filename, options) - testSpec r, makeTest(rodfilesDir / filename, options, cat) - - # test DLL interfacing: - test "gtkex1", true - test "gtkex2" +# ---------------- IC tests --------------------------------------------- + +proc icTests(r: var TResults; testsDir: string, cat: Category, options: string) = + const + tooltests = ["compiler/nim.nim", "tools/nimgrep.nim"] + writeOnly = " --incremental:writeonly " + readOnly = " --incremental:readonly " + incrementalOn = " --incremental:on " + + template test(x: untyped) = + testSpecWithNimcache(r, makeRawTest(file, x & options, cat), nimcache) + + template editedTest(x: untyped) = + var test = makeTest(file, x & options, cat) + test.spec.targets = {getTestSpecTarget()} + testSpecWithNimcache(r, test, nimcache) + + const tempExt = "_temp.nim" + for it in walkDirRec(testsDir / "ic"): + if isTestFile(it) and not it.endsWith(tempExt): + let nimcache = nimcacheDir(it, options, getTestSpecTarget()) + removeDir(nimcache) + + let content = readFile(it) + for fragment in content.split("#!EDIT!#"): + let file = it.replace(".nim", tempExt) + writeFile(file, fragment) + let oldPassed = r.passed + editedTest incrementalOn + if r.passed != oldPassed+1: break + + for file in tooltests: + let nimcache = nimcacheDir(file, options, getTestSpecTarget()) + removeDir(nimcache) + + let oldPassed = r.passed + test writeOnly + + if r.passed == oldPassed+1: + test readOnly + if r.passed == oldPassed+2: + test readOnly & "-d:nimBackendAssumesChange " # --------------------- flags tests ------------------------------------------- @@ -116,12 +112,6 @@ proc flagTests(r: var TResults, cat: Category, options: string) = # --------------------- DLL generation tests ---------------------------------- -proc safeCopyFile(src, dest: string) = - try: - copyFile(src, dest) - except OSError: - echo "[Warning] could not copy: ", src, " to ", dest - proc runBasicDLLTest(c, r: var TResults, cat: Category, options: string) = const rpath = when defined(macosx): " --passL:-rpath --passL:@loader_path" @@ -153,11 +143,12 @@ proc runBasicDLLTest(c, r: var TResults, cat: Category, options: string) = if "boehm" notin options: # force build required - see the comments in the .nim file for more details - var hcr_integration = makeTest("tests/dll/nimhcr_integration.nim", + var hcri = makeTest("tests/dll/nimhcr_integration.nim", options & " --forceBuild --hotCodeReloading:on" & rpath, cat) - hcr_integration.args = prepareTestArgs(hcr_integration.spec.getCmd, hcr_integration.name, - hcr_integration.options, getTestSpecTarget()) - testSpec r, hcr_integration + let nimcache = nimcacheDir(hcri.name, hcri.options, getTestSpecTarget()) + hcri.args = prepareTestArgs(hcri.spec.getCmd, hcri.name, + hcri.options, nimcache, getTestSpecTarget()) + testSpec r, hcri proc dllTests(r: var TResults, cat: Category, options: string) = # dummy compile result: @@ -363,7 +354,8 @@ proc testNimInAction(r: var TResults, cat: Category, options: string) = for i, test in tests: let filename = testsDir / test.addFileExt("nim") let testHash = getMD5(readFile(filename).string) - doAssert testHash == refHashes[i], "Nim in Action test " & filename & " was changed: " & $(i: i, testHash: testHash, refHash: refHashes[i]) + doAssert testHash == refHashes[i], "Nim in Action test " & filename & + " was changed: " & $(i: i, testHash: testHash, refHash: refHashes[i]) # Run the tests. for testfile in tests: test "tests/" & testfile & ".nim" @@ -449,25 +441,6 @@ let nimbleExe = findExe("nimble") packageIndex = nimbleDir / "packages_official.json" -proc waitForExitEx(p: Process): int = - var outp = outputStream(p) - var line = newStringOfCap(120).TaintedString - while true: - if outp.readLine(line): - discard - else: - result = peekExitCode(p) - if result != -1: break - close(p) - -proc getPackageDir(package: string): string = - ## TODO - Replace this with dom's version comparison magic. - let commandOutput = execCmdEx("nimble path $#" % package) - if commandOutput.exitCode != QuitSuccess: - return "" - else: - result = commandOutput[0].string - iterator listPackages(): tuple[name, url, cmd: string, hasDeps: bool] = let defaultCmd = "nimble test" let packageList = parseFile(packageIndex) @@ -481,8 +454,8 @@ iterator listPackages(): tuple[name, url, cmd: string, hasDeps: bool] = let name = package["name"].str if name == n: found = true - let p_url = package["url"].str - yield (name, p_url, cmd, hasDeps) + let pUrl = package["url"].str + yield (name, pUrl, cmd, hasDeps) break if not found: raise newException(ValueError, "Cannot find package '$#'." % n) @@ -551,7 +524,7 @@ proc testNimblePackages(r: var TResults, cat: Category) = # ---------------------------------------------------------------------------- -const AdditionalCategories = ["debugger", "examples", "lib"] +const AdditionalCategories = ["debugger", "examples", "lib", "ic"] const MegaTestCat = "megatest" proc `&.?`(a, b: string): string = @@ -584,17 +557,12 @@ proc isJoinableSpec(spec: TSpec): bool = (spec.targets == {} or spec.targets == {targetC}) proc norm(s: var string) = - # equivalent of s/\n+/\n/g (could use a single pass over input if needed) while true: let tmp = s.replace("\n\n", "\n") if tmp == s: break s = tmp s = s.strip -proc isTestFile*(file: string): bool = - let (_, name, ext) = splitFile(file) - result = ext == ".nim" and name.startsWith("t") - proc quoted(a: string): string = # todo: consider moving to system.nim result.addQuoted(a) @@ -607,10 +575,10 @@ proc runJoinedTest(r: var TResults, cat: Category, testsDir: string) = let cat = dir[testsDir.len .. ^1] if kind == pcDir and cat notin specialCategories: for file in walkDirRec(testsDir / cat): - if not isTestFile(file): continue - let spec = parseSpec(file) - if isJoinableSpec(spec): - specs.add spec + if isTestFile(file): + let spec = parseSpec(file) + if isJoinableSpec(spec): + specs.add spec proc cmp(a: TSpec, b:TSpec): auto = cmp(a.file, b.file) sort(specs, cmp=cmp) # reproducible order @@ -646,14 +614,13 @@ proc runJoinedTest(r: var TResults, cat: Category, testsDir: string) = let args = ["c", "--nimCache:" & outDir, "-d:testing", "--listCmd", "--listFullPaths:off", "--excessiveStackTrace:off", "megatest.nim"] - proc onStdout(line: string) = echo line + var (cmdLine, buf, exitCode) = execCmdEx2(command = compilerPrefix, args = args, input = "") if exitCode != 0: echo "$ ", cmdLine echo buf.string quit("megatest compilation failed") - # Could also use onStdout here. (buf, exitCode) = execCmdEx("./megatest") if exitCode != 0: echo buf.string @@ -690,6 +657,8 @@ proc processCategory(r: var TResults, cat: Category, when false: compileRodFiles(r, cat, options) runRodFiles(r, cat, options) + of "ic": + icTests(r, testsDir, cat, options) of "js": # only run the JS tests on Windows or Linux because Travis is bad # and other OSes like Haiku might lack nodejs: diff --git a/testament/htmlgen.nim b/testament/htmlgen.nim index 641c1c32c..67e44a2f8 100644 --- a/testament/htmlgen.nim +++ b/testament/htmlgen.nim @@ -9,7 +9,7 @@ ## HTML generator for the tester. -import cgi, backend, strutils, json, os, tables, times +import strutils, json, os, times import "testamenthtml.nimf" diff --git a/testament/important_packages.nim b/testament/important_packages.nim index defd748ab..93aeb1bbc 100644 --- a/testament/important_packages.nim +++ b/testament/important_packages.nim @@ -1,4 +1,3 @@ -import strutils template pkg(name: string; cmd = "nimble test"; hasDeps = false; url = ""): untyped = packages.add((name, cmd, hasDeps, url)) diff --git a/testament/specs.nim b/testament/specs.nim index 4d09f438f..cf3455027 100644 --- a/testament/specs.nim +++ b/testament/specs.nim @@ -7,7 +7,7 @@ # distribution, for details about the copyright. # -import sequtils, parseutils, strutils, os, osproc, streams, parsecfg +import sequtils, parseutils, strutils, os, streams, parsecfg var compilerPrefix* = findExe("nim") @@ -122,6 +122,9 @@ proc addLine*(self: var string; a,b: string) = self.add b self.add "\n" +proc initSpec*(filename: string): TSpec = + result.file = filename + proc parseSpec*(filename: string): TSpec = result.file = filename let specStr = extractSpec(filename) diff --git a/testament/tester.nim b/testament/tester.nim index 1e5bc875a..c32228ca8 100644 --- a/testament/tester.nim +++ b/testament/tester.nim @@ -10,9 +10,9 @@ ## This program verifies Nim against the testcases. import - parseutils, strutils, pegs, os, osproc, streams, parsecfg, json, - marshal, backend, parseopt, specs, htmlgen, browsers, terminal, - algorithm, times, sets, md5, sequtils + strutils, pegs, os, osproc, streams, json, + backend, parseopt, specs, htmlgen, browsers, terminal, + algorithm, times, md5, sequtils include compiler/nodejs @@ -124,19 +124,20 @@ proc execCmdEx2(command: string, args: openarray[string]; workingDir, input: str proc nimcacheDir(filename, options: string, target: TTarget): string = ## Give each test a private nimcache dir so they don't clobber each other's. let hashInput = options & $target - return "nimcache" / (filename & '_' & hashInput.getMD5) + result = "nimcache" / (filename & '_' & hashInput.getMD5) -proc prepareTestArgs(cmdTemplate, filename, options: string, +proc prepareTestArgs(cmdTemplate, filename, options, nimcache: string, target: TTarget, extraOptions=""): seq[string] = - let nimcache = nimcacheDir(filename, options, target) let options = options & " " & quoteShell("--nimCache:" & nimcache) & extraOptions - return parseCmdLine(cmdTemplate % ["target", targetToCmd[target], + result = parseCmdLine(cmdTemplate % ["target", targetToCmd[target], "options", options, "file", filename.quoteShell, "filedir", filename.getFileDir()]) -proc callCompiler(cmdTemplate, filename, options: string, - target: TTarget, extraOptions=""): TSpec = - let c = prepareTestArgs(cmdTemplate, filename, options, target, extraOptions) +proc callCompiler(cmdTemplate, filename, options, nimcache: string, + target: TTarget, + extraOptions=""): TSpec = + let c = prepareTestArgs(cmdTemplate, filename, options, nimcache, target, + extraOptions) result.cmd = quoteShellCommand(c) var p = startProcess(command=c[0], args=c[1 .. ^1], options={poStdErrToStdOut, poUsePath}) @@ -370,7 +371,7 @@ proc nimoutCheck(test: TTest; expectedNimout: string; given: var TSpec) = currentPos = giv.find(line.strip, currentPos) if currentPos < 0: given.err = reMsgsDiffer - return + break proc compilerOutputTests(test: TTest, target: TTarget, given: var TSpec, expected: TSpec; r: var TResults) = @@ -391,9 +392,9 @@ proc compilerOutputTests(test: TTest, target: TTarget, given: var TSpec, proc getTestSpecTarget(): TTarget = if getEnv("NIM_COMPILE_TO_CPP", "false").string == "true": - return targetCpp + result = targetCpp else: - return targetC + result = targetC proc checkDisabled(r: var TResults, test: TTest): bool = if test.spec.err in {reDisabled, reJoined}: @@ -401,13 +402,72 @@ proc checkDisabled(r: var TResults, test: TTest): bool = r.addResult(test, targetC, "", "", test.spec.err) inc(r.skipped) inc(r.total) - return - true + result = false + else: + result = true + +var count = 0 + +proc testSpecHelper(r: var TResults, test: TTest, expected: TSpec, target: TTarget, nimcache: string) = + case expected.action + of actionCompile: + var given = callCompiler(expected.getCmd, test.name, test.options, nimcache, target, + extraOptions = " --stdout --hint[Path]:off --hint[Processing]:off") + compilerOutputTests(test, target, given, expected, r) + of actionRun: + var given = callCompiler(expected.getCmd, test.name, test.options, nimcache, target) + if given.err != reSuccess: + r.addResult(test, target, "", "$ " & given.cmd & "\n" & given.nimout, given.err) + else: + let isJsTarget = target == targetJS + var exeFile = changeFileExt(test.name, if isJsTarget: "js" else: ExeExt) + if not existsFile(exeFile): + r.addResult(test, target, expected.output, + "executable not found: " & exeFile, reExeNotFound) + else: + let nodejs = if isJsTarget: findNodeJs() else: "" + if isJsTarget and nodejs == "": + r.addResult(test, target, expected.output, "nodejs binary not in PATH", + reExeNotFound) + else: + var exeCmd: string + var args = test.args + if isJsTarget: + exeCmd = nodejs + args = concat(@[exeFile], args) + else: + exeCmd = exeFile + var (_, buf, exitCode) = execCmdEx2(exeCmd, args, input = expected.input) + # Treat all failure codes from nodejs as 1. Older versions of nodejs used + # to return other codes, but for us it is sufficient to know that it's not 0. + if exitCode != 0: exitCode = 1 + let bufB = + if expected.sortoutput: + var x = splitLines(strip(buf.string)) + sort(x, system.cmp) + join(x, "\n") + else: + strip(buf.string) + if exitCode != expected.exitCode: + r.addResult(test, target, "exitcode: " & $expected.exitCode, + "exitcode: " & $exitCode & "\n\nOutput:\n" & + bufB, reExitCodesDiffer) + elif (expected.outputCheck == ocEqual and expected.output != bufB) or + (expected.outputCheck == ocSubstr and expected.output notin bufB): + given.err = reOutputsDiffer + r.addResult(test, target, expected.output, bufB, reOutputsDiffer) + else: + compilerOutputTests(test, target, given, expected, r) + of actionReject: + var given = callCompiler(expected.getCmd, test.name, test.options, + nimcache, target) + cmpMsgs(r, expected, given, test, target) + proc testSpec(r: var TResults, test: TTest, targets: set[TTarget] = {}) = var expected = test.spec if expected.parseErrors.len > 0: - # targetC is a lie, but parameter is required + # targetC is a lie, but a parameter is required r.addResult(test, targetC, "", expected.parseErrors, reInvalidSpec) inc(r.total) return @@ -422,73 +482,18 @@ proc testSpec(r: var TResults, test: TTest, targets: set[TTarget] = {}) = if target notin gTargets: r.addResult(test, target, "", "", reDisabled) inc(r.skipped) - continue - - if simulate: - var count {.global.} = 0 - count.inc + elif simulate: + inc count echo "testSpec count: ", count, " expected: ", expected - continue - - case expected.action - of actionCompile: - var given = callCompiler(expected.getCmd, test.name, test.options, target, - extraOptions=" --stdout --hint[Path]:off --hint[Processing]:off") - compilerOutputTests(test, target, given, expected, r) - of actionRun: - # In this branch of code "early return" pattern is clearer than deep - # nested conditionals - the empty rows in between to clarify the "danger" - var given = callCompiler(expected.getCmd, test.name, test.options, target) - if given.err != reSuccess: - r.addResult(test, target, "", "$ " & given.cmd & "\n" & given.nimout, given.err) - continue - let isJsTarget = target == targetJS - var exeFile = changeFileExt(test.name, if isJsTarget: "js" else: ExeExt) - if not existsFile(exeFile): - r.addResult(test, target, expected.output, - "executable not found: " & exeFile, reExeNotFound) - continue - - let nodejs = if isJsTarget: findNodeJs() else: "" - if isJsTarget and nodejs == "": - r.addResult(test, target, expected.output, "nodejs binary not in PATH", - reExeNotFound) - continue - var exeCmd: string - var args = test.args - if isJsTarget: - exeCmd = nodejs - args = concat(@[exeFile], args) - else: - exeCmd = exeFile - var (cmdLine, buf, exitCode) = execCmdEx2(exeCmd, args, input = expected.input) - # Treat all failure codes from nodejs as 1. Older versions of nodejs used - # to return other codes, but for us it is sufficient to know that it's not 0. - if exitCode != 0: exitCode = 1 - let bufB = - if expected.sortoutput: - var x = splitLines(strip(buf.string)) - sort(x, system.cmp) - join(x, "\n") - else: - strip(buf.string) - if exitCode != expected.exitCode: - r.addResult(test, target, "exitcode: " & $expected.exitCode, - "exitcode: " & $exitCode & "\n\nOutput:\n" & - bufB, reExitCodesDiffer) - continue - if (expected.outputCheck == ocEqual and expected.output != bufB) or - (expected.outputCheck == ocSubstr and expected.output notin bufB): - given.err = reOutputsDiffer - r.addResult(test, target, expected.output, bufB, reOutputsDiffer) - continue - compilerOutputTests(test, target, given, expected, r) - continue - of actionReject: - var given = callCompiler(expected.getCmd, test.name, test.options, - target) - cmpMsgs(r, expected, given, test, target) - continue + else: + let nimcache = nimcacheDir(test.name, test.options, target) + testSpecHelper(r, test, expected, target, nimcache) + +proc testSpecWithNimcache(r: var TResults, test: TTest; nimcache: string) = + if not checkDisabled(r, test): return + for target in test.spec.targets: + inc(r.total) + testSpecHelper(r, test, test.spec, target, nimcache) proc testC(r: var TResults, test: TTest, action: TTestAction) = # runs C code. Doesn't support any specs, just goes by exit code. @@ -529,6 +534,15 @@ proc makeTest(test, options: string, cat: Category): TTest = result.spec = parseSpec(addFileExt(test, ".nim")) result.startTime = epochTime() +proc makeRawTest(test, options: string, cat: Category): TTest = + result.cat = cat + result.name = test + result.options = options + result.spec = initSpec(addFileExt(test, ".nim")) + result.startTime = epochTime() + result.spec.action = actionCompile + result.spec.targets = {getTestSpecTarget()} + # TODO: fix these files const disabledFilesDefault = @[ "LockFreeHash.nim", @@ -553,23 +567,18 @@ when defined(windows): else: const # array of modules disabled from compilation test of stdlib. - # TODO: why the ["-"]? (previous code should've prob used seq[string] = @[] instead) - disabledFiles = disabledFilesDefault & @["-"] + disabledFiles = disabledFilesDefault include categories proc loadSkipFrom(name: string): seq[string] = - if name.len() == 0: return - + if name.len == 0: return # One skip per line, comments start with # # used by `nlvm` (at least) - try: - for line in lines(name): - let sline = line.strip() - if sline.len > 0 and not sline.startsWith("#"): - result.add sline - except: - echo "Could not load " & name & ", ignoring" + for line in lines(name): + let sline = line.strip() + if sline.len > 0 and not sline.startsWith("#"): + result.add sline proc main() = os.putenv "NIMTEST_COLOR", "never" diff --git a/tests/ic/tgenerics.nim b/tests/ic/tgenerics.nim new file mode 100644 index 000000000..bc5c05f4f --- /dev/null +++ b/tests/ic/tgenerics.nim @@ -0,0 +1,39 @@ +discard """ + output: "bar" + disabled: "true" +""" + +import tables + +var tab: Table[string, string] + +tab["foo"] = "bar" +echo tab["foo"] + +#!EDIT!# + +discard """ + output: "bar 3" +""" + +import tables + +var tab: Table[string, string] +var tab2: Table[string, int] + +tab["foo"] = "bar" +tab2["meh"] = 3 +echo tab["foo"], " ", tab2["meh"] + +#!EDIT!# + +discard """ + output: "3" +""" + +import tables + +var tab2: Table[string, int] + +tab2["meh"] = 3 +echo tab2["meh"] diff --git a/tests/ic/thallo.nim b/tests/ic/thallo.nim new file mode 100644 index 000000000..7ead7c8ba --- /dev/null +++ b/tests/ic/thallo.nim @@ -0,0 +1,28 @@ +discard """ + output: "Hello World" +""" + +const str = "Hello World" +echo str + +# Splitters are done with this special comment: + +#!EDIT!# + +discard """ + output: "Hello World B" +""" + +const str = "Hello World" +echo str, " B" + +#!EDIT!# + +discard """ + output: "Hello World C" +""" + +const str = "Hello World" +var x = 7 +if 3+4 == x: + echo str, " C" |