summary refs log tree commit diff stats
path: root/tests
diff options
context:
space:
mode:
authorDominik Picheta <dominikpicheta@gmail.com>2017-10-01 17:17:40 +0100
committerDominik Picheta <dominikpicheta@gmail.com>2017-10-01 17:17:40 +0100
commit7889c03cbc50afaa67e1e0eedb4fdcc577913bcd (patch)
tree96f310842e9313166e69a0a4ccdd74645f1a9098 /tests
parenta585748f2747bfa9f5e9d5585a74928a9fd13dc5 (diff)
downloadNim-7889c03cbc50afaa67e1e0eedb4fdcc577913bcd.tar.gz
Add tests for examples from Nim in Action.
Diffstat (limited to 'tests')
-rw-r--r--tests/niminaction/Chapter3/ChatApp/readme.markdown26
-rw-r--r--tests/niminaction/Chapter3/ChatApp/src/client.nim54
-rw-r--r--tests/niminaction/Chapter3/ChatApp/src/client.nim.cfg1
-rw-r--r--tests/niminaction/Chapter3/ChatApp/src/protocol.nim55
-rw-r--r--tests/niminaction/Chapter3/ChatApp/src/server.nim84
-rw-r--r--tests/niminaction/Chapter6/WikipediaStats/concurrency.nim79
-rw-r--r--tests/niminaction/Chapter6/WikipediaStats/concurrency.nim.cfg1
-rw-r--r--tests/niminaction/Chapter6/WikipediaStats/concurrency_regex.nim64
-rw-r--r--tests/niminaction/Chapter6/WikipediaStats/concurrency_regex.nim.cfg1
-rw-r--r--tests/niminaction/Chapter6/WikipediaStats/naive.nim29
-rw-r--r--tests/niminaction/Chapter6/WikipediaStats/parallel_counts.nim72
-rw-r--r--tests/niminaction/Chapter6/WikipediaStats/parallel_counts.nim.cfg1
-rw-r--r--tests/niminaction/Chapter6/WikipediaStats/race_condition.nim13
-rw-r--r--tests/niminaction/Chapter6/WikipediaStats/race_condition.nim.cfg1
-rw-r--r--tests/niminaction/Chapter6/WikipediaStats/sequential_counts.nim34
-rw-r--r--tests/niminaction/Chapter6/WikipediaStats/unguarded_access.nim15
-rw-r--r--tests/niminaction/Chapter6/WikipediaStats/unguarded_access.nim.cfg1
-rw-r--r--tests/niminaction/Chapter7/Tweeter/Tweeter.nimble14
-rw-r--r--tests/niminaction/Chapter7/Tweeter/public/style.css117
-rw-r--r--tests/niminaction/Chapter7/Tweeter/src/createDatabase.nim6
-rw-r--r--tests/niminaction/Chapter7/Tweeter/src/database.nim93
-rw-r--r--tests/niminaction/Chapter7/Tweeter/src/tweeter.nim62
-rw-r--r--tests/niminaction/Chapter7/Tweeter/src/views/general.nim51
-rw-r--r--tests/niminaction/Chapter7/Tweeter/src/views/user.nim49
-rw-r--r--tests/niminaction/Chapter7/Tweeter/tests/database_test.nim28
-rw-r--r--tests/niminaction/Chapter7/Tweeter/tests/database_test.nims2
-rw-r--r--tests/niminaction/Chapter8/canvas/canvas.nim19
-rw-r--r--tests/niminaction/Chapter8/canvas/canvas_test.nim19
-rw-r--r--tests/niminaction/Chapter8/sdl/sdl.nim34
-rw-r--r--tests/niminaction/Chapter8/sdl/sdl_test.nim25
-rw-r--r--tests/niminaction/Chapter8/sfml/sfml.nim26
-rw-r--r--tests/niminaction/Chapter8/sfml/sfml_test.nim9
-rw-r--r--tests/niminaction/Chapter9/configurator/configurator.nim84
-rw-r--r--tests/testament/categories.nim44
34 files changed, 1213 insertions, 0 deletions
diff --git a/tests/niminaction/Chapter3/ChatApp/readme.markdown b/tests/niminaction/Chapter3/ChatApp/readme.markdown
new file mode 100644
index 000000000..200b4df1d
--- /dev/null
+++ b/tests/niminaction/Chapter3/ChatApp/readme.markdown
@@ -0,0 +1,26 @@
+# The ChatApp source code
+
+This directory contains the ChatApp project, which is the project that is
+created as part of Chapter 3 of the Nim in Action book.
+
+To compile run:
+
+```
+nim c src/client
+nim c src/server
+```
+
+You can then run the ``server`` in one terminal by executing ``./src/server``.
+
+After doing so you can execute multiple clients in different terminals and have
+them communicate via the server.
+
+To execute a client, make sure to specify the server address and user name
+on the command line:
+
+```bash
+./src/client localhost Peter
+```
+
+You should then be able to start typing in messages and sending them
+by pressing the Enter key.
\ No newline at end of file
diff --git a/tests/niminaction/Chapter3/ChatApp/src/client.nim b/tests/niminaction/Chapter3/ChatApp/src/client.nim
new file mode 100644
index 000000000..4d139655c
--- /dev/null
+++ b/tests/niminaction/Chapter3/ChatApp/src/client.nim
@@ -0,0 +1,54 @@
+import os, threadpool, asyncdispatch, asyncnet
+import protocol
+
+proc connect(socket: AsyncSocket, serverAddr: string) {.async.} =
+  ## Connects the specified AsyncSocket to the specified address.
+  ## Then receives messages from the server continuously.
+  echo("Connecting to ", serverAddr)
+  # Pause the execution of this procedure until the socket connects to
+  # the specified server.
+  await socket.connect(serverAddr, 7687.Port)
+  echo("Connected!")
+  while true:
+    # Pause the execution of this procedure until a new message is received
+    # from the server.
+    let line = await socket.recvLine()
+    # Parse the received message using ``parseMessage`` defined in the
+    # protocol module.
+    let parsed = parseMessage(line)
+    # Display the message to the user.
+    echo(parsed.username, " said ", parsed.message)
+
+echo("Chat application started")
+# Ensure that the correct amount of command line arguments was specified.
+if paramCount() < 2:
+  # Terminate the client early with an error message if there was not
+  # enough command line arguments specified by the user.
+  quit("Please specify the server address, e.g. ./client localhost username")
+
+# Retrieve the first command line argument.
+let serverAddr = paramStr(1)
+# Retrieve the second command line argument.
+let username = paramStr(2)
+# Initialise a new asynchronous socket.
+var socket = newAsyncSocket()
+
+# Execute the ``connect`` procedure in the background asynchronously.
+asyncCheck connect(socket, serverAddr)
+# Execute the ``readInput`` procedure in the background in a new thread.
+var messageFlowVar = spawn stdin.readLine()
+while true:
+  # Check if the ``readInput`` procedure returned a new line of input.
+  if messageFlowVar.isReady():
+    # If a new line of input was returned, we can safely retrieve it
+    # without blocking.
+    # The ``createMessage`` is then used to create a message based on the
+    # line of input. The message is then sent in the background asynchronously.
+    asyncCheck socket.send(createMessage(username, ^messageFlowVar))
+    # Execute the ``readInput`` procedure again, in the background in a
+    # new thread.
+    messageFlowVar = spawn stdin.readLine()
+
+  # Execute the asyncdispatch event loop, to continue the execution of
+  # asynchronous procedures.
+  asyncdispatch.poll()
diff --git a/tests/niminaction/Chapter3/ChatApp/src/client.nim.cfg b/tests/niminaction/Chapter3/ChatApp/src/client.nim.cfg
new file mode 100644
index 000000000..aed303eef
--- /dev/null
+++ b/tests/niminaction/Chapter3/ChatApp/src/client.nim.cfg
@@ -0,0 +1 @@
+--threads:on
diff --git a/tests/niminaction/Chapter3/ChatApp/src/protocol.nim b/tests/niminaction/Chapter3/ChatApp/src/protocol.nim
new file mode 100644
index 000000000..af515861c
--- /dev/null
+++ b/tests/niminaction/Chapter3/ChatApp/src/protocol.nim
@@ -0,0 +1,55 @@
+import json
+
+type
+  Message* = object
+    username*: string
+    message*: string
+
+  MessageParsingError* = object of Exception
+
+proc parseMessage*(data: string): Message {.raises: [MessageParsingError, KeyError].} =
+  var dataJson: JsonNode
+  try:
+    dataJson = parseJson(data)
+  except JsonParsingError:
+    raise newException(MessageParsingError, "Invalid JSON: " &
+                       getCurrentExceptionMsg())
+  except:
+    raise newException(MessageParsingError, "Unknown error: " &
+                       getCurrentExceptionMsg())
+
+  if not dataJson.hasKey("username"):
+    raise newException(MessageParsingError, "Username field missing")
+
+  result.username = dataJson["username"].getStr()
+  if result.username.len == 0:
+    raise newException(MessageParsingError, "Username field is empty")
+
+  if not dataJson.hasKey("message"):
+    raise newException(MessageParsingError, "Message field missing")
+  result.message = dataJson["message"].getStr()
+  if result.message.len == 0:
+    raise newException(MessageParsingError, "Message field is empty")
+
+proc createMessage*(username, message: string): string =
+  result = $(%{
+    "username": %username,
+    "message": %message
+  }) & "\c\l"
+
+when isMainModule:
+  block:
+    let data = """{"username": "dom", "message": "hello"}"""
+    let parsed = parseMessage(data)
+    doAssert parsed.message == "hello"
+    doAssert parsed.username == "dom"
+
+  # Test failure
+  block:
+    try:
+      let parsed = parseMessage("asdasd")
+    except MessageParsingError:
+      doAssert true
+    except:
+      doAssert false
+
diff --git a/tests/niminaction/Chapter3/ChatApp/src/server.nim b/tests/niminaction/Chapter3/ChatApp/src/server.nim
new file mode 100644
index 000000000..8c572aeb0
--- /dev/null
+++ b/tests/niminaction/Chapter3/ChatApp/src/server.nim
@@ -0,0 +1,84 @@
+import asyncdispatch, asyncnet
+
+type
+  Client = ref object
+    socket: AsyncSocket
+    netAddr: string
+    id: int
+    connected: bool
+
+  Server = ref object
+    socket: AsyncSocket
+    clients: seq[Client]
+
+proc newServer(): Server =
+  ## Constructor for creating a new ``Server``.
+  Server(socket: newAsyncSocket(), clients: @[])
+
+proc `$`(client: Client): string =
+  ## Converts a ``Client``'s information into a string.
+  $client.id & "(" & client.netAddr & ")"
+
+proc processMessages(server: Server, client: Client) {.async.} =
+  ## Loops while ``client`` is connected to this server, and checks
+  ## whether as message has been received from ``client``.
+  while true:
+    # Pause execution of this procedure until a line of data is received from
+    # ``client``.
+    let line = await client.socket.recvLine()
+
+    # The ``recvLine`` procedure returns ``""`` (i.e. a string of length 0)
+    # when ``client`` has disconnected.
+    if line.len == 0:
+      echo(client, " disconnected!")
+      client.connected = false
+      # When a socket disconnects it must be closed.
+      client.socket.close()
+      return
+
+    # Display the message that was sent by the client.
+    echo(client, " sent: ", line)
+
+    # Send the message to other clients.
+    for c in server.clients:
+      # Don't send it to the client that sent this or to a client that is
+      # disconnected.
+      if c.id != client.id and c.connected:
+        await c.socket.send(line & "\c\l")
+
+proc loop(server: Server, port = 7687) {.async.} =
+  ## Loops forever and checks for new connections.
+
+  # Bind the port number specified by ``port``.
+  server.socket.bindAddr(port.Port)
+  # Ready the server socket for new connections.
+  server.socket.listen()
+  echo("Listening on localhost:", port)
+
+  while true:
+    # Pause execution of this procedure until a new connection is accepted.
+    let (netAddr, clientSocket) = await server.socket.acceptAddr()
+    echo("Accepted connection from ", netAddr)
+
+    # Create a new instance of Client.
+    let client = Client(
+      socket: clientSocket,
+      netAddr: netAddr,
+      id: server.clients.len,
+      connected: true
+    )
+    # Add this new instance to the server's list of clients.
+    server.clients.add(client)
+    # Run the ``processMessages`` procedure asynchronously in the background,
+    # this procedure will continuously check for new messages from the client.
+    asyncCheck processMessages(server, client)
+
+# Check whether this module has been imported as a dependency to another
+# module, or whether this module is the main module.
+when isMainModule:
+  # Initialise a new server.
+  var server = newServer()
+  echo("Server initialised!")
+  # Execute the ``loop`` procedure. The ``waitFor`` procedure will run the
+  # asyncdispatch event loop until the ``loop`` procedure finishes executing.
+  waitFor loop(server)
\ No newline at end of file
diff --git a/tests/niminaction/Chapter6/WikipediaStats/concurrency.nim b/tests/niminaction/Chapter6/WikipediaStats/concurrency.nim
new file mode 100644
index 000000000..478f533d9
--- /dev/null
+++ b/tests/niminaction/Chapter6/WikipediaStats/concurrency.nim
@@ -0,0 +1,79 @@
+# See this page for info about the format https://wikitech.wikimedia.org/wiki/Analytics/Data/Pagecounts-all-sites
+import tables, parseutils, strutils, threadpool
+
+const filename = "pagecounts-20160101-050000"
+
+type
+  Stats = ref object
+    projectName, pageTitle: string
+    requests, contentSize: int
+
+proc `$`(stats: Stats): string =
+  "(projectName: $#, pageTitle: $#, requests: $#, contentSize: $#)" % [
+    stats.projectName, stats.pageTitle, $stats.requests, $stats.contentSize
+  ]
+
+proc parse(chunk: string): Stats =
+  # Each line looks like: en Main_Page 242332 4737756101
+  result = Stats(projectName: "", pageTitle: "", requests: 0, contentSize: 0)
+
+  var projectName = ""
+  var pageTitle = ""
+  var requests = ""
+  var contentSize = ""
+  for line in chunk.splitLines:
+    var i = 0
+    projectName.setLen(0)
+    i.inc parseUntil(line, projectName, Whitespace, i)
+    i.inc skipWhitespace(line, i)
+    pageTitle.setLen(0)
+    i.inc parseUntil(line, pageTitle, Whitespace, i)
+    i.inc skipWhitespace(line, i)
+    requests.setLen(0)
+    i.inc parseUntil(line, requests, Whitespace, i)
+    i.inc skipWhitespace(line, i)
+    contentSize.setLen(0)
+    i.inc parseUntil(line, contentSize, Whitespace, i)
+    i.inc skipWhitespace(line, i)
+
+    if requests.len == 0 or contentSize.len == 0:
+      # Ignore lines with either of the params that are empty.
+      continue
+
+    let requestsInt = requests.parseInt
+    if requestsInt > result.requests and projectName == "en":
+      result = Stats(
+        projectName: projectName,
+        pageTitle: pageTitle,
+        requests: requestsInt,
+        contentSize: contentSize.parseInt
+      )
+
+proc readChunks(filename: string, chunksize = 1000000): Stats =
+  result = Stats(projectName: "", pageTitle: "", requests: 0, contentSize: 0)
+  var file = open(filename)
+  var responses = newSeq[FlowVar[Stats]]()
+  var buffer = newString(chunksize)
+  var oldBufferLen = 0
+  while not endOfFile(file):
+    let readSize = file.readChars(buffer, oldBufferLen, chunksize - oldBufferLen) + oldBufferLen
+    var chunkLen = readSize
+
+    while chunkLen >= 0 and buffer[chunkLen - 1] notin NewLines:
+      # Find where the last line ends
+      chunkLen.dec
+
+    responses.add(spawn parse(buffer[0 .. <chunkLen]))
+    oldBufferLen = readSize - chunkLen
+    buffer[0 .. <oldBufferLen] = buffer[readSize - oldBufferLen .. ^1]
+
+  for resp in responses:
+    let statistic = ^resp
+    if statistic.requests > result.requests:
+      result = statistic
+
+  file.close()
+
+
+when isMainModule:
+  echo readChunks(filename)
diff --git a/tests/niminaction/Chapter6/WikipediaStats/concurrency.nim.cfg b/tests/niminaction/Chapter6/WikipediaStats/concurrency.nim.cfg
new file mode 100644
index 000000000..aed303eef
--- /dev/null
+++ b/tests/niminaction/Chapter6/WikipediaStats/concurrency.nim.cfg
@@ -0,0 +1 @@
+--threads:on
diff --git a/tests/niminaction/Chapter6/WikipediaStats/concurrency_regex.nim b/tests/niminaction/Chapter6/WikipediaStats/concurrency_regex.nim
new file mode 100644
index 000000000..8df3b6aeb
--- /dev/null
+++ b/tests/niminaction/Chapter6/WikipediaStats/concurrency_regex.nim
@@ -0,0 +1,64 @@
+# See this page for info about the format https://wikitech.wikimedia.org/wiki/Analytics/Data/Pagecounts-all-sites
+import tables, parseutils, strutils, threadpool, re
+
+const filename = "pagecounts-20160101-050000"
+
+type
+  Stats = ref object
+    projectName, pageTitle: string
+    requests, contentSize: int
+
+proc `$`(stats: Stats): string =
+  "(projectName: $#, pageTitle: $#, requests: $#, contentSize: $#)" % [
+    stats.projectName, stats.pageTitle, $stats.requests, $stats.contentSize
+  ]
+
+proc parse(chunk: string): Stats =
+  # Each line looks like: en Main_Page 242332 4737756101
+  result = Stats(projectName: "", pageTitle: "", requests: 0, contentSize: 0)
+
+  var matches: array[4, string]
+  var reg = re"([^\s]+)\s([^\s]+)\s(\d+)\s(\d+)"
+  for line in chunk.splitLines:
+
+    let start = find(line, reg, matches)
+    if start == -1: continue
+
+    let requestsInt = matches[2].parseInt
+    if requestsInt > result.requests and matches[0] == "en":
+      result = Stats(
+        projectName: matches[0],
+        pageTitle: matches[1],
+        requests: requestsInt,
+        contentSize: matches[3].parseInt
+      )
+
+proc readChunks(filename: string, chunksize = 1000000): Stats =
+  result = Stats(projectName: "", pageTitle: "", requests: 0, contentSize: 0)
+  var file = open(filename)
+  var responses = newSeq[FlowVar[Stats]]()
+  var buffer = newString(chunksize)
+  var oldBufferLen = 0
+  while not endOfFile(file):
+    let readSize = file.readChars(buffer, oldBufferLen, chunksize - oldBufferLen) + oldBufferLen
+    var chunkLen = readSize
+
+    while chunkLen >= 0 and buffer[chunkLen - 1] notin NewLines:
+      # Find where the last line ends
+      chunkLen.dec
+
+    responses.add(spawn parse(buffer[0 .. <chunkLen]))
+    oldBufferLen = readSize - chunkLen
+    buffer[0 .. <oldBufferLen] = buffer[readSize - oldBufferLen .. ^1]
+
+  echo("Spawns: ", responses.len)
+  for resp in responses:
+    let statistic = ^resp
+    if statistic.requests > result.requests:
+      result = statistic
+
+  file.close()
+
+
+when isMainModule:
+  echo readChunks(filename)
diff --git a/tests/niminaction/Chapter6/WikipediaStats/concurrency_regex.nim.cfg b/tests/niminaction/Chapter6/WikipediaStats/concurrency_regex.nim.cfg
new file mode 100644
index 000000000..aed303eef
--- /dev/null
+++ b/tests/niminaction/Chapter6/WikipediaStats/concurrency_regex.nim.cfg
@@ -0,0 +1 @@
+--threads:on
diff --git a/tests/niminaction/Chapter6/WikipediaStats/naive.nim b/tests/niminaction/Chapter6/WikipediaStats/naive.nim
new file mode 100644
index 000000000..ed4fba8e2
--- /dev/null
+++ b/tests/niminaction/Chapter6/WikipediaStats/naive.nim
@@ -0,0 +1,29 @@
+# See this page for info about the format https://wikitech.wikimedia.org/wiki/Analytics/Data/Pagecounts-all-sites
+import tables, parseutils, strutils
+
+const filename = "pagecounts-20150101-050000"
+
+proc parse(filename: string): tuple[projectName, pageTitle: string,
+    requests, contentSize: int] =
+  # Each line looks like: en Main_Page 242332 4737756101
+  var file = open(filename)
+  for line in file.lines:
+    var i = 0
+    var projectName = ""
+    i.inc parseUntil(line, projectName, Whitespace, i)
+    i.inc
+    var pageTitle = ""
+    i.inc parseUntil(line, pageTitle, Whitespace, i)
+    i.inc
+    var requests = 0
+    i.inc parseInt(line, requests, i)
+    i.inc
+    var contentSize = 0
+    i.inc parseInt(line, contentSize, i)
+    if requests > result[2] and projectName == "en":
+      result = (projectName, pageTitle, requests, contentSize)
+
+  file.close()
+
+when isMainModule:
+  echo parse(filename)
diff --git a/tests/niminaction/Chapter6/WikipediaStats/parallel_counts.nim b/tests/niminaction/Chapter6/WikipediaStats/parallel_counts.nim
new file mode 100644
index 000000000..7181145e9
--- /dev/null
+++ b/tests/niminaction/Chapter6/WikipediaStats/parallel_counts.nim
@@ -0,0 +1,72 @@
+import os, parseutils, threadpool, strutils
+
+type
+  Stats = ref object
+    domainCode, pageTitle: string
+    countViews, totalSize: int
+
+proc newStats(): Stats =
+  Stats(domainCode: "", pageTitle: "", countViews: 0, totalSize: 0)
+
+proc `$`(stats: Stats): string =
+  "(domainCode: $#, pageTitle: $#, countViews: $#, totalSize: $#)" % [
+    stats.domainCode, stats.pageTitle, $stats.countViews, $stats.totalSize
+  ]
+
+proc parse(line: string, domainCode, pageTitle: var string,
+    countViews, totalSize: var int) =
+  if line.len == 0: return
+  var i = 0
+  domainCode.setLen(0)
+  i.inc parseUntil(line, domainCode, {' '}, i)
+  i.inc
+  pageTitle.setLen(0)
+  i.inc parseUntil(line, pageTitle, {' '}, i)
+  i.inc
+  countViews = 0
+  i.inc parseInt(line, countViews, i)
+  i.inc
+  totalSize = 0
+  i.inc parseInt(line, totalSize, i)
+
+proc parseChunk(chunk: string): Stats =
+  result = newStats()
+  var domainCode = ""
+  var pageTitle = ""
+  var countViews = 0
+  var totalSize = 0
+  for line in splitLines(chunk):
+    parse(line, domainCode, pageTitle, countViews, totalSize)
+    if domainCode == "en" and countViews > result.countViews:
+      result = Stats(domainCode: domainCode, pageTitle: pageTitle,
+                     countViews: countViews, totalSize: totalSize)
+
+proc readPageCounts(filename: string, chunkSize = 1_000_000) =
+  var file = open(filename)
+  var responses = newSeq[FlowVar[Stats]]()
+  var buffer = newString(chunksize)
+  var oldBufferLen = 0
+  while not endOfFile(file):
+    let reqSize = chunksize - oldBufferLen
+    let readSize = file.readChars(buffer, oldBufferLen, reqSize) + oldBufferLen
+    var chunkLen = readSize
+
+    while chunkLen >= 0 and buffer[chunkLen - 1] notin NewLines:
+      chunkLen.dec
+
+    responses.add(spawn parseChunk(buffer[0 .. <chunkLen]))
+    oldBufferLen = readSize - chunkLen
+    buffer[0 .. <oldBufferLen] = buffer[readSize - oldBufferLen .. ^1]
+
+  var mostPopular = newStats()
+  for resp in responses:
+    let statistic = ^resp
+    if statistic.countViews > mostPopular.countViews:
+      mostPopular = statistic
+
+  echo("Most popular is: ", mostPopular)
+
+when isMainModule:
+  const file = "pagecounts-20160101-050000"
+  let filename = getCurrentDir() / file
+  readPageCounts(filename)
\ No newline at end of file
diff --git a/tests/niminaction/Chapter6/WikipediaStats/parallel_counts.nim.cfg b/tests/niminaction/Chapter6/WikipediaStats/parallel_counts.nim.cfg
new file mode 100644
index 000000000..9d57ecf93
--- /dev/null
+++ b/tests/niminaction/Chapter6/WikipediaStats/parallel_counts.nim.cfg
@@ -0,0 +1 @@
+--threads:on
\ No newline at end of file
diff --git a/tests/niminaction/Chapter6/WikipediaStats/race_condition.nim b/tests/niminaction/Chapter6/WikipediaStats/race_condition.nim
new file mode 100644
index 000000000..c62b2f93e
--- /dev/null
+++ b/tests/niminaction/Chapter6/WikipediaStats/race_condition.nim
@@ -0,0 +1,13 @@
+import threadpool
+
+var counter = 0
+
+proc increment(x: int) =
+  for i in 0 .. <x:
+    let value = counter + 1
+    counter = value
+
+spawn increment(10_000)
+spawn increment(10_000)
+sync()
+echo(counter)
\ No newline at end of file
diff --git a/tests/niminaction/Chapter6/WikipediaStats/race_condition.nim.cfg b/tests/niminaction/Chapter6/WikipediaStats/race_condition.nim.cfg
new file mode 100644
index 000000000..9d57ecf93
--- /dev/null
+++ b/tests/niminaction/Chapter6/WikipediaStats/race_condition.nim.cfg
@@ -0,0 +1 @@
+--threads:on
\ No newline at end of file
diff --git a/tests/niminaction/Chapter6/WikipediaStats/sequential_counts.nim b/tests/niminaction/Chapter6/WikipediaStats/sequential_counts.nim
new file mode 100644
index 000000000..25ad7d5f4
--- /dev/null
+++ b/tests/niminaction/Chapter6/WikipediaStats/sequential_counts.nim
@@ -0,0 +1,34 @@
+import os, parseutils
+
+proc parse(line: string, domainCode, pageTitle: var string,
+    countViews, totalSize: var int) =
+  var i = 0
+  domainCode.setLen(0)
+  i.inc parseUntil(line, domainCode, {' '}, i)
+  i.inc
+  pageTitle.setLen(0)
+  i.inc parseUntil(line, pageTitle, {' '}, i)
+  i.inc
+  countViews = 0
+  i.inc parseInt(line, countViews, i)
+  i.inc
+  totalSize = 0
+  i.inc parseInt(line, totalSize, i)
+
+proc readPageCounts(filename: string) =
+  var domainCode = ""
+  var pageTitle = ""
+  var countViews = 0
+  var totalSize = 0
+  var mostPopular = ("", "", 0, 0)
+  for line in filename.lines:
+    parse(line, domainCode, pageTitle, countViews, totalSize)
+    if domainCode == "en" and countViews > mostPopular[2]:
+      mostPopular = (domainCode, pageTitle, countViews, totalSize)
+
+  echo("Most popular is: ", mostPopular)
+
+when isMainModule:
+  const file = "pagecounts-20160101-050000"
+  let filename = getCurrentDir() / file
+  readPageCounts(filename)
\ No newline at end of file
diff --git a/tests/niminaction/Chapter6/WikipediaStats/unguarded_access.nim b/tests/niminaction/Chapter6/WikipediaStats/unguarded_access.nim
new file mode 100644
index 000000000..72e8bff12
--- /dev/null
+++ b/tests/niminaction/Chapter6/WikipediaStats/unguarded_access.nim
@@ -0,0 +1,15 @@
+import threadpool, locks
+
+var counterLock: Lock
+initLock(counterLock)
+var counter {.guard: counterLock.} = 0
+
+proc increment(x: int) =
+  for i in 0 .. <x:
+    let value = counter + 1
+    counter = value
+
+spawn increment(10_000)
+spawn increment(10_000)
+sync()
+echo(counter)
diff --git a/tests/niminaction/Chapter6/WikipediaStats/unguarded_access.nim.cfg b/tests/niminaction/Chapter6/WikipediaStats/unguarded_access.nim.cfg
new file mode 100644
index 000000000..9d57ecf93
--- /dev/null
+++ b/tests/niminaction/Chapter6/WikipediaStats/unguarded_access.nim.cfg
@@ -0,0 +1 @@
+--threads:on
\ No newline at end of file
diff --git a/tests/niminaction/Chapter7/Tweeter/Tweeter.nimble b/tests/niminaction/Chapter7/Tweeter/Tweeter.nimble
new file mode 100644
index 000000000..0a0ffad1a
--- /dev/null
+++ b/tests/niminaction/Chapter7/Tweeter/Tweeter.nimble
@@ -0,0 +1,14 @@
+# Package
+
+version       = "0.1.0"
+author        = "Dominik Picheta"
+description   = "A simple Twitter clone developed in Nim in Action."
+license       = "MIT"
+
+bin = @["tweeter"]
+skipExt = @["nim"]
+
+# Dependencies
+
+requires "nim >= 0.13.1"
+requires "jester >= 0.0.1"
diff --git a/tests/niminaction/Chapter7/Tweeter/public/style.css b/tests/niminaction/Chapter7/Tweeter/public/style.css
new file mode 100644
index 000000000..baacfaf9d
--- /dev/null
+++ b/tests/niminaction/Chapter7/Tweeter/public/style.css
@@ -0,0 +1,117 @@
+body {
+  background-color: #f1f9ea;
+  margin: 0;
+  font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
+}
+
+div#main {
+  width: 80%;
+  margin-left: auto;
+  margin-right: auto;
+}
+
+div#user {
+  background-color: #66ac32;
+  width: 100%;
+  color: #c7f0aa;
+  padding: 5pt;
+}
+
+div#user > h1 {
+  color: #ffffff;
+}
+
+h1 {
+  margin: 0;
+  display: inline;
+  padding-left: 10pt;
+  padding-right: 10pt;
+}
+
+div#user > form {
+  float: right;
+  margin-right: 10pt;
+}
+
+div#user > form > input[type="submit"] {
+  border: 0px none;
+  padding: 5pt;
+  font-size: 108%;
+  color: #ffffff;
+  background-color: #515d47;
+  border-radius: 5px;
+  cursor: pointer;
+}
+
+div#user > form > input[type="submit"]:hover {
+  background-color: #538c29;
+}
+
+
+div#messages {
+  background-color: #a2dc78;
+  width: 90%;
+  margin-left: auto;
+  margin-right: auto;
+  color: #1a1a1a;
+}
+
+div#messages > div {
+  border-left: 1px solid #869979;
+  border-right: 1px solid #869979;
+  border-bottom: 1px solid #869979;
+  padding: 5pt;
+}
+
+div#messages > div > a, div#messages > div > span {
+  color: #475340;
+}
+
+div#messages > div > a:hover {
+  text-decoration: none;
+  color: #c13746;
+}
+
+h3 {
+  margin-bottom: 0;
+  font-weight: normal;
+}
+
+div#login {
+  width: 200px;
+  margin-left: auto;
+  margin-right: auto;
+  margin-top: 20%;
+
+  font-size: 130%;
+}
+
+div#login span.small {
+  display: block;
+  font-size: 56%;
+}
+
+div#newMessage {
+  background-color: #538c29;
+  width: 90%;
+  margin-left: auto;
+  margin-right: auto;
+  color: #ffffff;
+  padding: 5pt;
+}
+
+div#newMessage span {
+  padding-right: 5pt;
+}
+
+div#newMessage form {
+  display: inline;
+}
+
+div#newMessage > form > input[type="text"] {
+  width: 80%;
+}
+
+div#newMessage > form > input[type="submit"] {
+  font-size: 80%;
+}
diff --git a/tests/niminaction/Chapter7/Tweeter/src/createDatabase.nim b/tests/niminaction/Chapter7/Tweeter/src/createDatabase.nim
new file mode 100644
index 000000000..c7aee1b44
--- /dev/null
+++ b/tests/niminaction/Chapter7/Tweeter/src/createDatabase.nim
@@ -0,0 +1,6 @@
+import database
+
+var db = newDatabase()
+db.setup()
+echo("Database created successfully!")
+db.close()
\ No newline at end of file
diff --git a/tests/niminaction/Chapter7/Tweeter/src/database.nim b/tests/niminaction/Chapter7/Tweeter/src/database.nim
new file mode 100644
index 000000000..4faba3f6a
--- /dev/null
+++ b/tests/niminaction/Chapter7/Tweeter/src/database.nim
@@ -0,0 +1,93 @@
+import times, db_sqlite, strutils #<1>
+type #<2>
+  Database* = ref object
+    db*: DbConn
+
+  User* = object #<3>
+    username*: string #<4>
+    following*: seq[string] #<5>
+
+  Message* = object #<6>
+    username*: string #<7>
+    time*: Time #<8>
+    msg*: string #<9>
+
+proc newDatabase*(filename = "tweeter.db"): Database =
+  new result
+  result.db = open(filename, "", "", "")
+
+proc close*(database: Database) =
+ database.db.close()
+
+proc setup*(database: Database) =
+  database.db.exec(sql"""
+    CREATE TABLE IF NOT EXISTS User(
+      username text PRIMARY KEY
+    );
+  """)
+
+  database.db.exec(sql"""
+    CREATE TABLE IF NOT EXISTS Following(
+      follower text,
+      followed_user text,
+      PRIMARY KEY (follower, followed_user),
+      FOREIGN KEY (follower) REFERENCES User(username),
+      FOREIGN KEY (followed_user) REFERENCES User(username)
+    );
+  """)
+
+  database.db.exec(sql"""
+    CREATE TABLE IF NOT EXISTS Message(
+      username text,
+      time integer,
+      msg text NOT NULL,
+      FOREIGN KEY (username) REFERENCES User(username)
+    );
+  """)
+
+proc post*(database: Database, message: Message) =
+  if message.msg.len > 140: #<1>
+    raise newException(ValueError, "Message has to be less than 140 characters.")
+
+  database.db.exec(sql"INSERT INTO Message VALUES (?, ?, ?);", #<2>
+    message.username, $message.time.toSeconds().int, message.msg) #<3>
+
+proc follow*(database: Database, follower: User, user: User) =
+  database.db.exec(sql"INSERT INTO Following VALUES (?, ?);",#<2>
+    follower.username, user.username)
+
+proc create*(database: Database, user: User) =
+  database.db.exec(sql"INSERT INTO User VALUES (?);", user.username) #<2>
+
+proc findUser*(database: Database, username: string, user: var User): bool =
+  let row = database.db.getRow(
+      sql"SELECT username FROM User WHERE username = ?;", username)
+  if row[0].len == 0: return false
+  else: user.username = row[0]
+
+  let following = database.db.getAllRows(
+      sql"SELECT followed_user FROM Following WHERE follower = ?;", username)
+  user.following = @[]
+  for row in following:
+    if row[0].len != 0:
+      user.following.add(row[0])
+
+  return true
+
+proc findMessages*(database: Database, usernames: seq[string],
+    limit = 10): seq[Message] =
+  result = @[]
+  if usernames.len == 0: return
+  var whereClause = " WHERE "
+  for i in 0 .. <usernames.len:
+    whereClause.add("username = ? ")
+    if i != <usernames.len:
+      whereClause.add("or ")
+
+  let messages = database.db.getAllRows(
+      sql("SELECT username, time, msg FROM Message" &
+          whereClause &
+          "ORDER BY time DESC LIMIT " & $limit),
+      usernames)
+  for row in messages:
+    result.add(Message(username: row[0], time: fromSeconds(row[1].parseInt), msg: row[2]))
diff --git a/tests/niminaction/Chapter7/Tweeter/src/tweeter.nim b/tests/niminaction/Chapter7/Tweeter/src/tweeter.nim
new file mode 100644
index 000000000..b8a36306e
--- /dev/null
+++ b/tests/niminaction/Chapter7/Tweeter/src/tweeter.nim
@@ -0,0 +1,62 @@
+import asyncdispatch, times
+
+import jester
+
+import database, views/user, views/general
+
+proc userLogin(db: Database, request: Request, user: var User): bool =
+  if request.cookies.hasKey("username"):
+    if not db.findUser(request.cookies["username"], user):
+      user = User(username: request.cookies["username"], following: @[])
+      db.create(user)
+    return true
+  else:
+    return false
+
+let db = newDatabase()
+routes:
+  get "/":
+    var user: User
+    if db.userLogin(request, user):
+      let messages = db.findMessages(user.following & user.username)
+      resp renderMain(renderTimeline(user.username, messages))
+    else:
+      resp renderMain(renderLogin())
+
+  get "/@name":
+    cond '.' notin @"name"
+    var user: User
+    if not db.findUser(@"name", user):
+      halt "User not found"
+    let messages = db.findMessages(@[user.username])
+
+    var currentUser: User
+    if db.userLogin(request, currentUser):
+      resp renderMain(renderUser(user, currentUser) & renderMessages(messages))
+    else:
+      resp renderMain(renderUser(user) & renderMessages(messages))
+
+  post "/follow":
+    var follower: User
+    var target: User
+    if not db.findUser(@"follower", follower):
+      halt "Follower not found"
+    if not db.findUser(@"target", target):
+      halt "Follow target not found"
+    db.follow(follower, target)
+    redirect(uri("/" & @"target"))
+
+  post "/login":
+    setCookie("username", @"username", getTime().getGMTime() + 2.hours)
+    redirect("/")
+
+  post "/createMessage":
+    let message = Message(
+      username: @"username",
+      time: getTime(),
+      msg: @"message"
+    )
+    db.post(message)
+    redirect("/")
+
+runForever()
diff --git a/tests/niminaction/Chapter7/Tweeter/src/views/general.nim b/tests/niminaction/Chapter7/Tweeter/src/views/general.nim
new file mode 100644
index 000000000..0e920b1de
--- /dev/null
+++ b/tests/niminaction/Chapter7/Tweeter/src/views/general.nim
@@ -0,0 +1,51 @@
+#? stdtmpl(subsChar = '$', metaChar = '#')
+#import "../database"
+#import user
+#import xmltree
+#
+#proc `$!`(text: string): string = escape(text)
+#end proc
+#
+#proc renderMain*(body: string): string =
+#  result = ""
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tweeter written in Nim</title>
+    <link rel="stylesheet" type="text/css" href="style.css">
+  </head>
+
+  <body>
+    ${body}
+  </body>
+
+</html>
+#end proc
+#
+#proc renderLogin*(): string =
+#  result = ""
+<div id="login">
+  <span>Login</span>
+  <span class="small">Please type in your username...</span>
+  <form action="login" method="post">
+    <input type="text" name="username">
+    <input type="submit" value="Login">
+  </form>
+</div>
+#end proc
+#
+#proc renderTimeline*(username: string, messages: seq[Message]): string =
+#  result = ""
+<div id="user">
+  <h1>${$!username}'s timeline</h1>
+</div>
+<div id="newMessage">
+  <span>New message</span>
+  <form action="createMessage" method="post">
+    <input type="text" name="message">
+    <input type="hidden" name="username" value="${$!username}">
+    <input type="submit" value="Tweet">
+  </form>
+</div>
+${renderMessages(messages)}
+#end proc
\ No newline at end of file
diff --git a/tests/niminaction/Chapter7/Tweeter/src/views/user.nim b/tests/niminaction/Chapter7/Tweeter/src/views/user.nim
new file mode 100644
index 000000000..f3791b493
--- /dev/null
+++ b/tests/niminaction/Chapter7/Tweeter/src/views/user.nim
@@ -0,0 +1,49 @@
+#? stdtmpl(subsChar = '$', metaChar = '#', toString = "xmltree.escape")
+#import "../database"
+#import xmltree
+#import times
+#
+#proc renderUser*(user: User): string =
+#  result = ""
+<div id="user">
+  <h1>${user.username}</h1>
+  <span>Following: ${$user.following.len}</span>
+</div>
+#end proc
+#
+#proc renderUser*(user: User, currentUser: User): string =
+#  result = ""
+<div id="user">
+  <h1>${user.username}</h1>
+  <span>Following: ${$user.following.len}</span>
+  #if user.username notin currentUser.following:
+  <form action="follow" method="post">
+    <input type="hidden" name="follower" value="${currentUser.username}">
+    <input type="hidden" name="target" value="${user.username}">
+    <input type="submit" value="Follow">
+  </form>
+  #end if
+</div>
+#
+#end proc
+#
+#proc renderMessages*(messages: seq[Message]): string =
+#  result = ""
+<div id="messages">
+  #for message in messages:
+    <div>
+      <a href="/${message.username}">${message.username}</a>
+      <span>${message.time.getGMTime().format("HH:mm MMMM d',' yyyy")}</span>
+      <h3>${message.msg}</h3>
+    </div>
+  #end for
+</div>
+#end proc
+#
+#when isMainModule:
+#  echo renderUser(User(username: "d0m96<>", following: @[]))
+#  echo renderMessages(@[
+#    Message(username: "d0m96", time: getTime(), msg: "Hello World!"),
+#    Message(username: "d0m96", time: getTime(), msg: "Testing")
+#  ])
+#end when
diff --git a/tests/niminaction/Chapter7/Tweeter/tests/database_test.nim b/tests/niminaction/Chapter7/Tweeter/tests/database_test.nim
new file mode 100644
index 000000000..926ca452c
--- /dev/null
+++ b/tests/niminaction/Chapter7/Tweeter/tests/database_test.nim
@@ -0,0 +1,28 @@
+import database, os, times
+
+when isMainModule:
+  removeFile("tweeter_test.db")
+  var db = newDatabase("tweeter_test.db")
+  db.setup()
+
+  db.create(User(username: "d0m96"))
+  db.create(User(username: "nim_lang"))
+
+  db.post(Message(username: "nim_lang", time: getTime() - 4.seconds,
+      msg: "Hello Nim in Action readers"))
+  db.post(Message(username: "nim_lang", time: getTime(),
+      msg: "99.9% off Nim in Action for everyone, for the next minute only!"))
+
+  var dom: User
+  doAssert db.findUser("d0m96", dom)
+  var nim: User
+  doAssert db.findUser("nim_lang", nim)
+  db.follow(dom, nim)
+
+  doAssert db.findUser("d0m96", dom)
+
+  let messages = db.findMessages(dom.following)
+  echo(messages)
+  doAssert(messages[0].msg == "99.9% off Nim in Action for everyone, for the next minute only!")
+  doAssert(messages[1].msg == "Hello Nim in Action readers")
+  echo("All tests finished successfully!")
diff --git a/tests/niminaction/Chapter7/Tweeter/tests/database_test.nims b/tests/niminaction/Chapter7/Tweeter/tests/database_test.nims
new file mode 100644
index 000000000..226905fbf
--- /dev/null
+++ b/tests/niminaction/Chapter7/Tweeter/tests/database_test.nims
@@ -0,0 +1,2 @@
+--path:"../src"
+#switch("path", "./src")
diff --git a/tests/niminaction/Chapter8/canvas/canvas.nim b/tests/niminaction/Chapter8/canvas/canvas.nim
new file mode 100644
index 000000000..713d1e9e2
--- /dev/null
+++ b/tests/niminaction/Chapter8/canvas/canvas.nim
@@ -0,0 +1,19 @@
+import dom
+
+type
+  CanvasRenderingContext* = ref object
+    fillStyle* {.importc.}: cstring
+    strokeStyle* {.importc.}: cstring
+
+{.push importcpp.}
+
+proc getContext*(canvasElement: Element,
+    contextType: cstring): CanvasRenderingContext
+
+proc fillRect*(context: CanvasRenderingContext, x, y, width, height: int)
+
+proc moveTo*(context: CanvasRenderingContext, x, y: int)
+
+proc lineTo*(context: CanvasRenderingContext, x, y: int)
+
+proc stroke*(context: CanvasRenderingContext)
diff --git a/tests/niminaction/Chapter8/canvas/canvas_test.nim b/tests/niminaction/Chapter8/canvas/canvas_test.nim
new file mode 100644
index 000000000..42d222b7b
--- /dev/null
+++ b/tests/niminaction/Chapter8/canvas/canvas_test.nim
@@ -0,0 +1,19 @@
+import canvas, dom
+
+proc onLoad() {.exportc.} =
+  var canvas = document.getElementById("canvas").EmbedElement
+  canvas.width = window.innerWidth
+  canvas.height = window.innerHeight
+  var ctx = canvas.getContext("2d")
+
+  ctx.fillStyle = "#1d4099"
+  ctx.fillRect(0, 0, window.innerWidth, window.innerHeight)
+
+  ctx.strokeStyle = "#ffffff"
+  let letterWidth = 100
+  let letterLeftPos = (window.innerWidth div 2) - (letterWidth div 2)
+  ctx.moveTo(letterLeftPos, 320)
+  ctx.lineTo(letterLeftPos, 110)
+  ctx.lineTo(letterLeftPos + letterWidth, 320)
+  ctx.lineTo(letterLeftPos + letterWidth, 110)
+  ctx.stroke()
diff --git a/tests/niminaction/Chapter8/sdl/sdl.nim b/tests/niminaction/Chapter8/sdl/sdl.nim
new file mode 100644
index 000000000..a1b30281b
--- /dev/null
+++ b/tests/niminaction/Chapter8/sdl/sdl.nim
@@ -0,0 +1,34 @@
+when defined(Windows):
+  const libName* = "SDL2.dll"
+elif defined(Linux):
+  const libName* = "libSDL2.so"
+elif defined(MacOsX):
+  const libName* = "libSDL2.dylib"
+
+type
+  SdlWindow = object
+  SdlWindowPtr* = ptr SdlWindow
+  SdlRenderer = object
+  SdlRendererPtr* = ptr SdlRenderer
+
+const INIT_VIDEO* = 0x00000020
+
+{.push dynlib: libName.}
+proc init*(flags: uint32): cint {.importc: "SDL_Init".}
+
+proc createWindowAndRenderer*(width, height: cint, window_flags: cuint,
+    window: var SdlWindowPtr, renderer: var SdlRendererPtr): cint
+    {.importc: "SDL_CreateWindowAndRenderer".}
+
+proc pollEvent*(event: pointer): cint {.importc: "SDL_PollEvent".}
+
+proc setDrawColor*(renderer: SdlRendererPtr, r, g, b, a: uint8): cint
+    {.importc: "SDL_SetRenderDrawColor", discardable.}
+
+proc present*(renderer: SdlRendererPtr) {.importc: "SDL_RenderPresent".}
+
+proc clear*(renderer: SdlRendererPtr) {.importc: "SDL_RenderClear".}
+
+proc drawLines*(renderer: SdlRendererPtr, points: ptr tuple[x, y: cint],
+    count: cint): cint {.importc: "SDL_RenderDrawLines", discardable.}
+{.pop.}
diff --git a/tests/niminaction/Chapter8/sdl/sdl_test.nim b/tests/niminaction/Chapter8/sdl/sdl_test.nim
new file mode 100644
index 000000000..a572d5231
--- /dev/null
+++ b/tests/niminaction/Chapter8/sdl/sdl_test.nim
@@ -0,0 +1,25 @@
+import os
+import sdl
+
+if sdl.init(INIT_VIDEO) == -1:
+  quit("Couldn't initialise SDL")
+
+var window: SdlWindowPtr
+var renderer: SdlRendererPtr
+if createWindowAndRenderer(640, 480, 0, window, renderer) == -1:
+  quit("Couldn't create a window or renderer")
+
+discard pollEvent(nil)
+renderer.setDrawColor 29, 64, 153, 255
+renderer.clear
+renderer.setDrawColor 255, 255, 255, 255
+var points = [
+  (260'i32, 320'i32),
+  (260'i32, 110'i32),
+  (360'i32, 320'i32),
+  (360'i32, 110'i32)
+]
+renderer.drawLines(addr points[0], points.len.cint)
+
+renderer.present
+sleep(5000)
diff --git a/tests/niminaction/Chapter8/sfml/sfml.nim b/tests/niminaction/Chapter8/sfml/sfml.nim
new file mode 100644
index 000000000..fea85fcd4
--- /dev/null
+++ b/tests/niminaction/Chapter8/sfml/sfml.nim
@@ -0,0 +1,26 @@
+{.passL: "-lsfml-graphics -lsfml-system -lsfml-window".}
+
+type
+  VideoMode* {.importcpp: "sf::VideoMode".} = object
+  RenderWindowObj {.importcpp: "sf::RenderWindow".} = object
+  RenderWindow* = ptr RenderWindowObj
+  Color* {.importcpp: "sf::Color".} = object
+  Event* {.importcpp: "sf::Event".} = object
+
+{.push cdecl, header: "<SFML/Graphics.hpp>".}
+
+proc videoMode*(modeWidth, modeHeight: cuint, modeBitsPerPixel: cuint = 32): VideoMode
+    {.importcpp: "sf::VideoMode(@)", constructor.}
+
+proc newRenderWindow*(mode: VideoMode, title: cstring): RenderWindow
+    {.importcpp: "new sf::RenderWindow(@)", constructor.}
+
+proc pollEvent*(window: RenderWindow, event: var Event): bool
+    {.importcpp: "#.pollEvent(@)".}
+
+proc newColor*(red, green, blue, alpha: uint8): Color
+    {.importcpp: "sf::Color(@)", constructor.}
+
+proc clear*(window: RenderWindow, color: Color) {.importcpp: "#.clear(@)".}
+
+proc display*(window: RenderWindow) {.importcpp: "#.display()".}
diff --git a/tests/niminaction/Chapter8/sfml/sfml_test.nim b/tests/niminaction/Chapter8/sfml/sfml_test.nim
new file mode 100644
index 000000000..49a8176e5
--- /dev/null
+++ b/tests/niminaction/Chapter8/sfml/sfml_test.nim
@@ -0,0 +1,9 @@
+import sfml, os
+var window = newRenderWindow(videoMode(800, 600), "SFML works!")
+
+var event: Event
+discard window.pollEvent(event)
+window.clear(newColor(29, 64, 153, 255))
+window.display()
+
+sleep(1000)
diff --git a/tests/niminaction/Chapter9/configurator/configurator.nim b/tests/niminaction/Chapter9/configurator/configurator.nim
new file mode 100644
index 000000000..0d5627889
--- /dev/null
+++ b/tests/niminaction/Chapter9/configurator/configurator.nim
@@ -0,0 +1,84 @@
+import macros
+
+proc createRefType(ident: NimIdent, identDefs: seq[NimNode]): NimNode =
+  result = newTree(nnkTypeSection,
+    newTree(nnkTypeDef,
+      newIdentNode(ident),
+      newEmptyNode(),
+      newTree(nnkRefTy,
+        newTree(nnkObjectTy,
+          newEmptyNode(),
+          newEmptyNode(),
+          newTree(nnkRecList,
+            identDefs
+          )
+        )
+      )
+    )
+  )
+
+proc toIdentDefs(stmtList: NimNode): seq[NimNode] =
+  expectKind(stmtList, nnkStmtList)
+  result = @[]
+
+  for child in stmtList:
+    expectKind(child, nnkCall)
+    result.add(newIdentDefs(child[0], child[1][0]))
+
+template constructor(ident: untyped): untyped =
+  proc `new ident`(): `ident` =
+    new result
+
+proc createLoadProc(typeName: NimIdent, identDefs: seq[NimNode]): NimNode =
+  var cfgIdent = newIdentNode("cfg")
+  var filenameIdent = newIdentNode("filename")
+  var objIdent = newIdentNode("obj")
+
+  var body = newStmtList()
+  body.add quote do:
+    var `objIdent` = parseFile(`filenameIdent`)
+
+  for identDef in identDefs:
+    let fieldNameIdent = identDef[0]
+    let fieldName = $fieldNameIdent.ident
+    case $identDef[1].ident
+    of "string":
+      body.add quote do:
+        `cfgIdent`.`fieldNameIdent` = `objIdent`[`fieldName`].getStr
+    of "int":
+      body.add quote do:
+        `cfgIdent`.`fieldNameIdent` = `objIdent`[`fieldName`].getNum().int
+    else:
+      doAssert(false, "Not Implemented")
+
+  return newProc(newIdentNode("load"),
+    [newEmptyNode(),
+     newIdentDefs(cfgIdent, newIdentNode(typeName)),
+     newIdentDefs(filenameIdent, newIdentNode("string"))],
+    body)
+
+macro config*(typeName: untyped, fields: untyped): untyped =
+  result = newStmtList()
+
+  let identDefs = toIdentDefs(fields)
+  result.add createRefType(typeName.ident, identDefs)
+  result.add getAst(constructor(typeName.ident))
+  result.add createLoadProc(typeName.ident, identDefs)
+
+  echo treeRepr(typeName)
+  echo treeRepr(fields)
+
+  echo treeRepr(result)
+  echo toStrLit(result)
+  # TODO: Verify that we can export fields in config type so that it can be
+  # used in another module.
+
+import json
+config MyAppConfig:
+  address: string
+  port: int
+
+var myConf = newMyAppConfig()
+myConf.load("myappconfig.cfg")
+echo("Address: ", myConf.address)
+echo("Port: ", myConf.port)
diff --git a/tests/testament/categories.nim b/tests/testament/categories.nim
index 7b1dd0df0..f71a4a1e7 100644
--- a/tests/testament/categories.nim
+++ b/tests/testament/categories.nim
@@ -238,6 +238,48 @@ proc jsTests(r: var TResults, cat: Category, options: string) =
   for testfile in ["strutils", "json", "random", "times", "logging"]:
     test "lib/pure/" & testfile & ".nim"
 
+# ------------------------- nim in action -----------
+
+proc testNimInAction(r: var TResults, cat: Category, options: string) =
+  template test(filename: untyped, action: untyped) =
+    testSpec r, makeTest(filename, options, cat, action)
+
+  template testJS(filename: untyped) =
+    testSpec r, makeTest(filename, options, cat, actionCompile, targetJS)
+
+  template testCPP(filename: untyped) =
+    testSpec r, makeTest(filename, options, cat, actionCompile, targetCPP)
+
+  let tests = [
+    "niminaction/Chapter3/ChatApp/src/server",
+    "niminaction/Chapter3/ChatApp/src/client",
+    "niminaction/Chapter6/WikipediaStats/concurrency_regex",
+    "niminaction/Chapter6/WikipediaStats/concurrency",
+    "niminaction/Chapter6/WikipediaStats/naive",
+    "niminaction/Chapter6/WikipediaStats/parallel_counts",
+    "niminaction/Chapter6/WikipediaStats/race_condition",
+    "niminaction/Chapter6/WikipediaStats/sequential_counts",
+    "niminaction/Chapter7/Tweeter/src/tweeter",
+    "niminaction/Chapter7/Tweeter/src/createDatabase",
+    "niminaction/Chapter7/Tweeter/tests/database_test",
+    "niminaction/Chapter8/sdl/sdl_test",
+    ]
+  for testfile in tests:
+    test "tests/" & testfile & ".nim", actionCompile
+
+  # TODO: This doesn't work for some reason ;\
+  # let reject = "tests/niminaction/Chapter6/WikipediaStats" &
+  #              "/unguarded_access.nim"
+  # test reject, actionReject
+
+  let jsFile = "tests/niminaction/Chapter8/canvas/canvas_test.nim"
+  testJS jsFile
+
+  let cppFile = "tests/niminaction/Chapter8/sfml/sfml_test.nim"
+  testCPP cppFile
+
+
+
 # ------------------------- manyloc -------------------------------------------
 #proc runSpecialTests(r: var TResults, options: string) =
 #  for t in ["lib/packages/docutils/highlite"]:
@@ -420,6 +462,8 @@ proc processCategory(r: var TResults, cat: Category, options: string) =
     testNimblePackages(r, cat, pfExtraOnly)
   of "nimble-all":
     testNimblePackages(r, cat, pfAll)
+  of "niminaction":
+    testNimInAction(r, cat, options)
   of "untestable":
     # We can't test it because it depends on a third party.
     discard # TODO: Move untestable tests to someplace else, i.e. nimble repo.