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
This commit is contained in:
Andreas Rumpf
2019-08-08 08:41:05 +02:00
committed by GitHub
parent c0d240b8cd
commit c8cffaf420
16 changed files with 318 additions and 261 deletions

View File

@@ -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")
# make sure this is pulled in (meaning hcrGetGlobal() is called for it during init)
discard cgsym(m, "programResult")
if m.inHcrInitGuard:
endBlock(m.initProc)
# 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 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")
if m.g.breakpoints != nil:
discard cgsym(m, "dbgRegisterBreakpoint")
if optEndb in m.config.options:
discard cgsym(m, "dbgRegisterFilename")
# 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.forwardedProcs.len == 0:
incl m.flags, objHasKidsValid
let disp = generateMethodDispatchers(graph)
for x in disp: genProcAux(m, x.sym)
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

View File

@@ -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")

View File

@@ -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)

View File

@@ -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:

View File

@@ -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:

View File

@@ -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 =

View File

@@ -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):

View File

@@ -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.

View File

@@ -10,7 +10,7 @@
#
const
NimbleStableCommit = "d15c8530cb7480ce39ffa85a2dd9819d2d4fc645" # 0.10.2
NimbleStableCommit = "da82e3111e662fc1b12f96b3cddd66c749c0f686" # master
when defined(gcc) and defined(windows):
when defined(x86):

View File

@@ -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"
# ---------------- IC tests ---------------------------------------------
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 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 "
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)
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)
# test basic recompilation scheme:
test "hallo", true
test "hallo"
when false:
# test incremental type information:
test "hallo2"
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)
# test type converters:
test "aconv", true
test "bconv"
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
# test G, A, B example from the documentation; test init sections:
test "deada", true
test "deada2"
for file in tooltests:
let nimcache = nimcacheDir(file, options, getTestSpecTarget())
removeDir(nimcache)
when false:
# test method generation:
test "bmethods", true
test "bmethods2"
let oldPassed = r.passed
test writeOnly
# 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"
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:

View File

@@ -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"

View File

@@ -1,4 +1,3 @@
import strutils
template pkg(name: string; cmd = "nimble test"; hasDeps = false; url = ""): untyped =
packages.add((name, cmd, hasDeps, url))

View File

@@ -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)

View File

@@ -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
else:
let nimcache = nimcacheDir(test.name, test.options, target)
testSpecHelper(r, test, expected, target, nimcache)
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
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"

39
tests/ic/tgenerics.nim Normal file
View File

@@ -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"]

28
tests/ic/thallo.nim Normal file
View File

@@ -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"