summary refs log tree commit diff stats
path: root/tests/dll
diff options
context:
space:
mode:
Diffstat (limited to 'tests/dll')
-rw-r--r--tests/dll/client.nim43
-rw-r--r--tests/dll/client.nim.cfg1
-rw-r--r--tests/dll/nimhcr_0.nim4
-rw-r--r--tests/dll/nimhcr_0_1.nim14
-rw-r--r--tests/dll/nimhcr_0_2.nim18
-rw-r--r--tests/dll/nimhcr_0_3.nim19
-rw-r--r--tests/dll/nimhcr_0_4.nim19
-rw-r--r--tests/dll/nimhcr_0_5.nim2
-rw-r--r--tests/dll/nimhcr_0_6.nim4
-rw-r--r--tests/dll/nimhcr_1.nim0
-rw-r--r--tests/dll/nimhcr_1_1.nim51
-rw-r--r--tests/dll/nimhcr_1_2.nim4
-rw-r--r--tests/dll/nimhcr_1_3.nim0
-rw-r--r--tests/dll/nimhcr_2.nim0
-rw-r--r--tests/dll/nimhcr_2_1.nim32
-rw-r--r--tests/dll/nimhcr_2_2.nim7
-rw-r--r--tests/dll/nimhcr_2_3.nim0
-rw-r--r--tests/dll/nimhcr_basic.nim8
-rw-r--r--tests/dll/nimhcr_integration.nim170
-rw-r--r--tests/dll/nimhcr_unit.nim149
-rw-r--r--tests/dll/nimhcr_unit.nim.cfg2
-rw-r--r--tests/dll/server.nim27
-rw-r--r--tests/dll/server.nim.cfg2
-rw-r--r--tests/dll/test_nimhcr_integration.bat10
-rwxr-xr-xtests/dll/test_nimhcr_integration.sh17
-rw-r--r--tests/dll/visibility.nim43
26 files changed, 646 insertions, 0 deletions
diff --git a/tests/dll/client.nim b/tests/dll/client.nim
new file mode 100644
index 000000000..62697569f
--- /dev/null
+++ b/tests/dll/client.nim
@@ -0,0 +1,43 @@
+discard """
+  cmd: "nim $target --debuginfo --hints:on --define:useNimRtl $options $file"
+"""
+
+type
+  TNodeKind = enum nkLit, nkSub, nkAdd, nkDiv, nkMul
+  TNode = object
+    case k: TNodeKind
+    of nkLit: x: int
+    else: a, b: ref TNode
+
+  PNode = ref TNode
+
+
+when defined(windows):
+  const dllname = "server.dll"
+elif defined(macosx):
+  const dllname = "libserver.dylib"
+else:
+  const dllname = "libserver.so"
+
+proc newLit(x: int): PNode {.importc: "newLit", dynlib: dllname.}
+proc newOp(k: TNodeKind, a, b: PNode): PNode {.
+  importc: "newOp", dynlib: dllname.}
+proc buildTree(x: int): PNode {.importc: "buildTree", dynlib: dllname.}
+
+proc eval(n: PNode): int =
+  case n.k
+  of nkLit: result = n.x
+  of nkSub: result = eval(n.a) - eval(n.b)
+  of nkAdd: result = eval(n.a) + eval(n.b)
+  of nkDiv: result = eval(n.a) div eval(n.b)
+  of nkMul: result = eval(n.a) * eval(n.b)
+
+# Test the GC:
+for i in 0..100_000:
+  discard eval(buildTree(2))
+
+# bug https://forum.nim-lang.org/t/8176; Error: ambiguous identifier: 'nimrtl'
+import std/strutils
+doAssert join(@[1, 2]) == "12"
+doAssert join(@[1.5, 2.5]) == "1.52.5"
+doAssert join(@["a", "bc"]) == "abc"
diff --git a/tests/dll/client.nim.cfg b/tests/dll/client.nim.cfg
new file mode 100644
index 000000000..0e044a829
--- /dev/null
+++ b/tests/dll/client.nim.cfg
@@ -0,0 +1 @@
+--define:useNimRtl
diff --git a/tests/dll/nimhcr_0.nim b/tests/dll/nimhcr_0.nim
new file mode 100644
index 000000000..fe0b29a36
--- /dev/null
+++ b/tests/dll/nimhcr_0.nim
@@ -0,0 +1,4 @@
+
+let g_0 = 1000 # new value! but also a "new" global :)
+
+proc getInt*(): int = return g_0
diff --git a/tests/dll/nimhcr_0_1.nim b/tests/dll/nimhcr_0_1.nim
new file mode 100644
index 000000000..620050be3
--- /dev/null
+++ b/tests/dll/nimhcr_0_1.nim
@@ -0,0 +1,14 @@
+
+import hotcodereloading
+
+let g_0 = 42 # lets start with the ultimate answer
+
+proc getInt*(): int = return g_0
+
+programResult = 0 # should be accessible
+
+beforeCodeReload:
+  echo "   0: before"
+afterCodeReload:
+  echo "   0: after"
+  
\ No newline at end of file
diff --git a/tests/dll/nimhcr_0_2.nim b/tests/dll/nimhcr_0_2.nim
new file mode 100644
index 000000000..9ce228dc1
--- /dev/null
+++ b/tests/dll/nimhcr_0_2.nim
@@ -0,0 +1,18 @@
+
+import hotcodereloading
+
+import nimhcr_1 # new import!
+
+# global scope for this module was executed when loading the program
+# with a previous version which didn't contain this print statement
+echo "   0: I SHOULDN'T BE PRINTED!"
+
+var g_0 = 0 # changed value but won't take effect
+
+proc getInt*(): int = return g_0 + g_1 + f_1()
+
+beforeCodeReload:
+  echo "   0: before - improved!" # changed handlers!
+afterCodeReload:
+  echo "   0: after - improved!"
+  g_0 = 100 # we cannot change it in its initialization but we can in the 'after' handler!
diff --git a/tests/dll/nimhcr_0_3.nim b/tests/dll/nimhcr_0_3.nim
new file mode 100644
index 000000000..183424e11
--- /dev/null
+++ b/tests/dll/nimhcr_0_3.nim
@@ -0,0 +1,19 @@
+
+import hotcodereloading
+
+import nimhcr_1
+import nimhcr_2 # a new and different import!
+
+proc makeCounter*(): auto =
+  return iterator: int {.closure.} =
+    for i in countup(0, 10, 1):
+      yield i
+
+let c = makeCounter()
+
+afterCodeReload:
+  echo "   0: after - closure iterator: ", c()
+  echo "   0: after - closure iterator: ", c()
+  echo "   0: after - c_2 = ", c_2
+
+proc getInt*(): int = return g_1 + g_2.len
diff --git a/tests/dll/nimhcr_0_4.nim b/tests/dll/nimhcr_0_4.nim
new file mode 100644
index 000000000..4471782a7
--- /dev/null
+++ b/tests/dll/nimhcr_0_4.nim
@@ -0,0 +1,19 @@
+
+import hotcodereloading
+
+import nimhcr_1 # only importing 1
+
+let g_0 = 1000 # new value! but also a "new" global :)
+
+proc getInt*(): int = return g_0
+
+proc makeCounter*(): auto =
+  return iterator: int {.closure.} =
+    for i in countup(0, 10, 1):
+      yield i
+
+let c = makeCounter()
+
+afterCodeReload:
+  echo "   0: after - closure iterator! after reload! does it remember? :", c()
+  echo "   0: after - closure iterator! after reload! does it remember? :", c()
diff --git a/tests/dll/nimhcr_0_5.nim b/tests/dll/nimhcr_0_5.nim
new file mode 100644
index 000000000..aff4014ca
--- /dev/null
+++ b/tests/dll/nimhcr_0_5.nim
@@ -0,0 +1,2 @@
+
+proc getInt*(): int = return 42 # back to the answer...
diff --git a/tests/dll/nimhcr_0_6.nim b/tests/dll/nimhcr_0_6.nim
new file mode 100644
index 000000000..fe0b29a36
--- /dev/null
+++ b/tests/dll/nimhcr_0_6.nim
@@ -0,0 +1,4 @@
+
+let g_0 = 1000 # new value! but also a "new" global :)
+
+proc getInt*(): int = return g_0
diff --git a/tests/dll/nimhcr_1.nim b/tests/dll/nimhcr_1.nim
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/dll/nimhcr_1.nim
diff --git a/tests/dll/nimhcr_1_1.nim b/tests/dll/nimhcr_1_1.nim
new file mode 100644
index 000000000..299c52baa
--- /dev/null
+++ b/tests/dll/nimhcr_1_1.nim
@@ -0,0 +1,51 @@
+
+echo "   1: print me once!"
+
+import hotcodereloading
+
+let g_1* = 8 # devilish!
+
+proc f_1*(): int =
+  var a {.global.} = 1
+  a.inc
+  return a
+
+
+# all these constructs should compile
+let some_glob_1 = 1
+echo "   1: ", some_glob_1
+if true:
+  let some_glob_2 = 2
+  echo "   1: ", some_glob_2
+  if true:
+    let some_glob_3 = 3
+    echo "   1: ", some_glob_3
+block:
+  let some_glob_4 = 4
+  proc inBlock(num: int) =
+    echo "   1: ", num
+  inBlock(some_glob_4)
+var counter = 3
+while counter > 0:
+  let some_glob_5 = 5
+  echo "   1: ", some_glob_5
+  counter.dec
+
+type
+  Type1 = object
+    a: int
+    b: int
+var t = Type1(a: 42, b: 11)
+echo "   1: Type1.a:", t.a
+
+type
+  obj = ref object
+    dat: int
+    str: string
+
+proc foo(): (int, obj) = (1, obj(dat: 3, str: "bar"))
+
+let (aa, bb) = foo()
+afterCodeReload:
+  echo aa
+  echo bb.str
diff --git a/tests/dll/nimhcr_1_2.nim b/tests/dll/nimhcr_1_2.nim
new file mode 100644
index 000000000..caf772450
--- /dev/null
+++ b/tests/dll/nimhcr_1_2.nim
@@ -0,0 +1,4 @@
+
+import nimhcr_2
+
+proc f_1*(): int = return f_2()
diff --git a/tests/dll/nimhcr_1_3.nim b/tests/dll/nimhcr_1_3.nim
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/dll/nimhcr_1_3.nim
diff --git a/tests/dll/nimhcr_2.nim b/tests/dll/nimhcr_2.nim
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/dll/nimhcr_2.nim
diff --git a/tests/dll/nimhcr_2_1.nim b/tests/dll/nimhcr_2_1.nim
new file mode 100644
index 000000000..705ed6d5a
--- /dev/null
+++ b/tests/dll/nimhcr_2_1.nim
@@ -0,0 +1,32 @@
+
+import hotcodereloading
+
+type
+  Type2 = ref object of RootObj
+    data*: int
+
+let g_2* = @[Type2(data: 2), Type2(data: 3)][1..^1] # should have a length of 1
+
+const c_2* = [1, 2, 3] # testing that a complext const object is properly exported
+
+var a: tuple[str: string, i: int]
+a.str = "   2: random string"
+echo a.str
+
+beforeCodeReload:
+  echo "   2: before!"
+
+# testing a construct of 2 functions in the same module which reference each other
+# https://github.com/nim-lang/Nim/issues/11608
+proc rec_1(depth: int)
+proc rec_2(depth: int) =
+  rec_1(depth + 1)
+proc rec_1(depth: int) =
+  if depth < 3:
+    rec_2(depth)
+  else:
+    echo("max mutual recursion reached!")
+
+# https://github.com/nim-lang/Nim/issues/11996
+let rec_2_func_ref = rec_2
+rec_2_func_ref(0)
diff --git a/tests/dll/nimhcr_2_2.nim b/tests/dll/nimhcr_2_2.nim
new file mode 100644
index 000000000..04ab2d3bb
--- /dev/null
+++ b/tests/dll/nimhcr_2_2.nim
@@ -0,0 +1,7 @@
+
+import hotcodereloading
+
+proc f_2*(): int = return 1
+
+afterCodeReload:
+  echo "   2: after!"
diff --git a/tests/dll/nimhcr_2_3.nim b/tests/dll/nimhcr_2_3.nim
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/dll/nimhcr_2_3.nim
diff --git a/tests/dll/nimhcr_basic.nim b/tests/dll/nimhcr_basic.nim
new file mode 100644
index 000000000..2e1f39ae0
--- /dev/null
+++ b/tests/dll/nimhcr_basic.nim
@@ -0,0 +1,8 @@
+discard """
+  output: '''
+Hello world
+'''
+"""
+# for now orc only tests successful compilation
+
+echo "Hello world"
diff --git a/tests/dll/nimhcr_integration.nim b/tests/dll/nimhcr_integration.nim
new file mode 100644
index 000000000..ac34f1f85
--- /dev/null
+++ b/tests/dll/nimhcr_integration.nim
@@ -0,0 +1,170 @@
+discard """
+  disabled: "true"
+  output: '''
+main: HELLO!
+main: hasAnyModuleChanged? true
+main: before
+   0: after
+main: after
+              The answer is: 1000
+main: hasAnyModuleChanged? false
+              The answer is: 1000
+main: hasAnyModuleChanged? true
+   0: before
+main: before
+   1: print me once!
+   1: 1
+   1: 2
+   1: 3
+   1: 4
+   1: 5
+   1: 5
+   1: 5
+   1: Type1.a:42
+1
+bar
+   0: after - improved!
+main: after
+              The answer is: 110
+main: hasAnyModuleChanged? true
+   0: before - improved!
+main: before
+   2: random string
+max mutual recursion reached!
+1
+bar
+   0: after - closure iterator: 0
+   0: after - closure iterator: 1
+   0: after - c_2 = [1, 2, 3]
+main: after
+              The answer is: 9
+main: hasAnyModuleChanged? true
+   2: before!
+main: before
+   2: after!
+   0: after - closure iterator! after reload! does it remember? :2
+   0: after - closure iterator! after reload! does it remember? :3
+main: after
+              The answer is: 1000
+main: hasAnyModuleChanged? true
+main: before
+main: after
+              The answer is: 42
+done
+'''
+"""
+
+#[
+xxx disabled: "openbsd" because it would otherwise give:
+/home/build/Nim/lib/nimhcr.nim(532) hcrInit
+/home/build/Nim/lib/nimhcr.nim(503) initModules
+/home/build/Nim/lib/nimhcr.nim(463) initPointerData
+/home/build/Nim/lib/nimhcr.nim(346) hcrRegisterProc
+/home/build/Nim/lib/pure/reservedmem.nim(223) setLen
+/home/build/Nim/lib/pure/reservedmem.nim(97) setLen
+/home/build/Nim/lib/pure/includes/oserr.nim(94) raiseOSError
+Error: unhandled exception: Not supported [OSError]
+
+After instrumenting code, the stacktrace actually points to the call to `check mprotect`
+]#
+
+## This is perhaps the most complex test in the nim test suite - calling the
+## compiler on the file itself with the same set or arguments and reloading
+## parts of the program at runtime! In the same folder there are a few modules
+## with names such as `nimhcr_<number>.nim`. Each of them has a few versions which
+## are in the format of `nimhcr_<number>_<version>.nim`. The below code uses the
+## `update` proc to say which of the modules should bump its version (and that
+## is done by copying `nimhcr_<number>_<version>.nim` onto `nimhcr_<number>.nim`).
+## The files should refer to each other (when importing) without the versions.
+## A few files can be updated by calling `update` for each of their indexes
+## and after that with a single call to `compileReloadExecute` the new version
+## of the program will be compiled, reloaded, and the only thing the main module
+## calls from `nimhcr_0.nim` (the procedure `getInt` proc) is called for a result.
+##
+## This test is expected to be executed with arguments - the full nim compiler
+## command used for building it - so it can rebuild iself the same way - example:
+##
+## compiling:
+##                   nim c --hotCodeReloading:on --nimCache:<folder> <this_file>.nim
+## executing:
+##   <this_file>.exe nim c --hotCodeReloading:on --nimCache:<folder> <this_file>.nim
+
+import os, osproc, strutils, hotcodereloading
+
+import nimhcr_0 # getInt() - the only thing we continually call from the main module
+
+proc compileReloadExecute() =
+  # Remove the `--forceBuild` option - is there in the first place because:
+  # - when `koch test` is ran for the first time the nimcache is empty
+  # - when each of the variants are built (debug, release after that, different GCs)
+  #   the main executable that gets built into the appropriate nimcache folder
+  #   gets copied to the originally intended destination and is executed
+  #   (this behaviour is only when the --hotCodeReloading option is used).
+  # - when `koch test` is ran again and the nimcache is full the executable files
+  #   in the nimcache folder aren't relinked and therefore aren't copied to the
+  #   originally intended destination - so when the binary at the intended
+  #   destination is executed - it is actually a remnant from a previous execution.
+  #   That is a problem because it points to shared objects to load from its own
+  #   nimcache folder - the one used for building it - a previous run! And when
+  #   this test changes other modules it references but the main module (this file)
+  #   remains intact - the binary isn't replaced. `--forceBuild` fixes this but has
+  #   to be applied only for the main build - the one done from koch, but when this
+  #   binary triggers rebuilding itself here it shouldn't rebuild the main module -
+  #   that would lead to replacing the main binary executable which is running!
+  let cmd = commandLineParams()[0..^1].join(" ").replace(" --forceBuild")
+  doAssert cmd.len > 0
+  let (stdout, exitcode) = execCmdEx(cmd)
+  if exitcode != 0:
+    echo "COMPILATION ERROR!"
+    echo "COMMAND: ", cmd
+    echo "STDOUT: ", stdout
+    quit 1
+  echo "main: hasAnyModuleChanged? ", hasAnyModuleChanged()
+  performCodeReload()
+  echo "              The answer is: ", getInt()
+
+# there are 3 files and all of them start from their 1st version
+var vers = [1, 1, 1]
+proc update(file: int) =
+  proc getfile(mid: string): string =
+    let (path, _, _) = splitFile(currentSourcePath())
+    return path & "/nimhcr_" & mid & ".nim"
+  copyFile(getfile($file & "_" & $vers[file]), getfile($file))
+  inc vers[file]
+
+beforeCodeReload:
+  echo "main: before"
+
+afterCodeReload:
+  echo "main: after"
+
+echo "main: HELLO!"
+
+update 0
+compileReloadExecute() # versions are: 1 - -
+
+compileReloadExecute() # no change
+
+update 0
+update 1
+compileReloadExecute() # versions are: 2 1 -
+
+update 0
+update 2
+compileReloadExecute() # versions are: 3 1 1
+
+update 0
+update 1
+update 2
+compileReloadExecute() # versions are: 4 2 2
+
+update 0
+compileReloadExecute() # versions are: 5 2 2
+
+# final update so there are no git modifications left after everything
+# (the last versions are like the first files without a version suffix)
+update 0
+update 1
+update 2
+
+echo "done"
\ No newline at end of file
diff --git a/tests/dll/nimhcr_unit.nim b/tests/dll/nimhcr_unit.nim
new file mode 100644
index 000000000..249f3f9f1
--- /dev/null
+++ b/tests/dll/nimhcr_unit.nim
@@ -0,0 +1,149 @@
+discard """
+disabled: "openbsd"
+disabled: "netbsd"
+output: '''
+fastcall_proc implementation #1 10
+11
+fastcall_proc implementation #2 20
+22
+fastcall_proc implementation #2 20
+22
+fastcall_proc implementation #3 30
+33
+fastcall_proc implementation #3 30
+33
+fastcall_proc implementation #3 30
+33
+fastcall_proc implementation #3 40
+43
+cdecl_proc implementation #1 10
+11
+cdecl_proc implementation #2 20
+22
+cdecl_proc implementation #2 20
+22
+cdecl_proc implementation #3 30
+33
+cdecl_proc implementation #3 30
+33
+cdecl_proc implementation #3 30
+33
+cdecl_proc implementation #3 40
+43
+stdcall_proc implementation #1 10
+11
+stdcall_proc implementation #2 20
+22
+stdcall_proc implementation #2 20
+22
+stdcall_proc implementation #3 30
+33
+stdcall_proc implementation #3 30
+33
+stdcall_proc implementation #3 30
+33
+stdcall_proc implementation #3 40
+43
+noconv_proc implementation #1 10
+11
+noconv_proc implementation #2 20
+22
+noconv_proc implementation #2 20
+22
+noconv_proc implementation #3 30
+33
+noconv_proc implementation #3 30
+33
+noconv_proc implementation #3 30
+33
+noconv_proc implementation #3 40
+43
+inline_proc implementation #1 10
+11
+inline_proc implementation #2 20
+22
+inline_proc implementation #2 20
+22
+inline_proc implementation #3 30
+33
+inline_proc implementation #3 30
+33
+inline_proc implementation #3 30
+33
+inline_proc implementation #3 40
+43
+'''
+"""
+
+import macros
+
+macro carryOutTests(callingConv: untyped): untyped =
+  let
+    procName = $callingConv & "_proc"
+    globalName = $callingConv & "_global"
+    callingConv = callingConv
+    p1 = ident(procName & "1")
+    p2 = ident(procName & "2")
+    p3 = ident(procName & "3")
+    g1 = ident(globalName & "1")
+    g2 = ident(globalName & "2")
+
+  result = quote do:
+    var `g1`: pointer = nil
+    if hcrRegisterGlobal("dummy_module", `globalName`, sizeof(int), nil, addr `g1`):
+      cast[ptr int](`g1`)[] = 10
+
+    var `g2`: pointer = nil
+    if hcrRegisterGlobal("dummy_module", `globalName`, sizeof(int), nil, addr `g2`):
+      cast[ptr int](`g2`)[] = 20
+
+    doAssert `g1` == `g2` and cast[ptr int](`g1`)[] == 10
+
+    type
+      F = proc (x: int): int {.placeholder.}
+
+    proc `p1`(x: int): int {.placeholder.}=
+      echo `procName`, " implementation #1 ", x
+      return x + 1
+
+    let fp1 = cast[F](hcrRegisterProc("dummy_module", `procName`, cast[pointer](`p1`)))
+    echo fp1(10)
+
+    proc `p2`(x: int): int {.placeholder.} =
+      echo `procName`, " implementation #2 ", x
+      return x + 2
+
+    let fp2 = cast[F](hcrRegisterProc("dummy_module", `procName`, cast[pointer](`p2`)))
+    echo fp1(20)
+    echo fp2(20)
+
+    proc `p3`(x: int): int {.placeholder.} =
+      echo `procName`, " implementation #3 ", x
+      return x + 3
+
+    let fp3 = cast[F](hcrRegisterProc("dummy_module", `procName`, cast[pointer](`p3`)))
+    echo fp1(30)
+    echo fp2(30)
+    echo fp3(30)
+
+    let fp4 = cast[F](hcrGetProc("dummy_module", `procName`))
+    echo fp4(40)
+
+  proc replacePlaceholderPragmas(n: NimNode) =
+    if n.kind == nnkPragma:
+      n[0] = callingConv
+    else:
+      for i in 0 ..< n.len:
+        replacePlaceholderPragmas n[i]
+
+  replacePlaceholderPragmas result
+  # echo result.treeRepr
+
+hcrAddModule("dummy_module")
+
+carryOutTests fastcall
+carryOutTests cdecl
+carryOutTests stdcall
+carryOutTests noconv
+carryOutTests inline
+
diff --git a/tests/dll/nimhcr_unit.nim.cfg b/tests/dll/nimhcr_unit.nim.cfg
new file mode 100644
index 000000000..b13c310d9
--- /dev/null
+++ b/tests/dll/nimhcr_unit.nim.cfg
@@ -0,0 +1,2 @@
+-d:useNimRtl
+-d:testNimHcr
diff --git a/tests/dll/server.nim b/tests/dll/server.nim
new file mode 100644
index 000000000..dd4606298
--- /dev/null
+++ b/tests/dll/server.nim
@@ -0,0 +1,27 @@
+discard """
+action: compile
+  cmd: "nim $target --debuginfo --hints:on --define:useNimRtl --app:lib $options $file"
+batchable: false
+"""
+
+type
+  TNodeKind = enum nkLit, nkSub, nkAdd, nkDiv, nkMul
+  TNode = object
+    case k: TNodeKind
+    of nkLit: x: int
+    else: a, b: ref TNode
+
+  PNode = ref TNode
+
+proc newLit(x: int): PNode {.exportc: "newLit", dynlib.} =
+  result = PNode(k: nkLit, x: x)
+
+proc newOp(k: TNodeKind, a, b: PNode): PNode {.exportc: "newOp", dynlib.} =
+  assert a != nil
+  assert b != nil
+  result = PNode(k: nkSub, a: a, b: b)
+  # now overwrite with the real value:
+  result.k = k
+
+proc buildTree(x: int): PNode {.exportc: "buildTree", dynlib.} =
+  result = newOp(nkMul, newOp(nkAdd, newLit(x), newLit(x)), newLit(x))
diff --git a/tests/dll/server.nim.cfg b/tests/dll/server.nim.cfg
new file mode 100644
index 000000000..02393ba8b
--- /dev/null
+++ b/tests/dll/server.nim.cfg
@@ -0,0 +1,2 @@
+--define:useNimRtl
+--app:lib
diff --git a/tests/dll/test_nimhcr_integration.bat b/tests/dll/test_nimhcr_integration.bat
new file mode 100644
index 000000000..66e6beac4
--- /dev/null
+++ b/tests/dll/test_nimhcr_integration.bat
@@ -0,0 +1,10 @@
+set NIM=nim
+set NIM_FLAGS=-d:debug
+
+%NIM% c --outdir:"." %NIM_FLAGS% ../../lib/nimrtl.nim
+%NIM% c --outdir:"." %NIM_FLAGS% ../../lib/nimhcr.nim
+
+set HCR_FLAGS=--forceBuild --hotCodeReloading:on --nimcache:nimcache %NIM_FLAGS%
+
+%NIM% %HCR_FLAGS% c nimhcr_integration.nim
+nimhcr_integration %NIM% %HCR_FLAGS% c nimhcr_integration.nim
diff --git a/tests/dll/test_nimhcr_integration.sh b/tests/dll/test_nimhcr_integration.sh
new file mode 100755
index 000000000..a2e2d0483
--- /dev/null
+++ b/tests/dll/test_nimhcr_integration.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+set -e
+
+rm -rf nimcache
+
+NIM_FLAGS=${*:- -d:debug}
+NIM=nim
+
+$NIM c --outdir:"." $NIM_FLAGS ../../lib/nimrtl.nim
+$NIM c --outdir:"." $NIM_FLAGS ../../lib/nimhcr.nim
+
+echo ===== Compiling HCR Integration Test =====
+HCR_FLAGS="--forceBuild --hotCodeReloading:on --nimcache:nimcache $NIM_FLAGS"
+$NIM $HCR_FLAGS c nimhcr_integration.nim
+export LD_LIBRARY_PATH=$(pwd):$LD_LIBRARY_PATH
+./nimhcr_integration $NIM $HCR_FLAGS c nimhcr_integration.nim
diff --git a/tests/dll/visibility.nim b/tests/dll/visibility.nim
new file mode 100644
index 000000000..ec15dad29
--- /dev/null
+++ b/tests/dll/visibility.nim
@@ -0,0 +1,43 @@
+discard """
+  output: ""
+"""
+
+const LibName {.used.} =
+  when defined(windows):
+    "visibility.dll"
+  elif defined(macosx):
+    "libvisibility.dylib"
+  else:
+    "libvisibility.so"
+
+when compileOption("app", "lib"):
+  var
+    bar {.exportc.}: int
+    thr {.exportc, threadvar.}: int
+  proc foo() {.exportc.} = discard
+
+  var
+    exported {.exportc, dynlib.}: int
+    exported_thr {.exportc, threadvar, dynlib.}: int
+  proc exported_func() {.exportc, dynlib.} = discard
+elif isMainModule:
+  import dynlib
+
+  let handle = loadLib(LibName)
+
+  template check(sym: untyped) =
+    const s = astToStr(sym)
+    if handle.symAddr(s) != nil:
+      echo s, " is exported"
+  template checkE(sym: untyped) =
+    const s = astToStr(sym)
+    if handle.symAddr(s) == nil:
+      echo s, " is not exported"
+
+  check foo
+  check bar
+  check thr
+
+  checkE exported
+  checkE exported_thr
+  checkE exported_func