From 7889c03cbc50afaa67e1e0eedb4fdcc577913bcd Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 1 Oct 2017 17:17:40 +0100 Subject: Add tests for examples from Nim in Action. --- tests/niminaction/Chapter3/ChatApp/readme.markdown | 26 +++++ tests/niminaction/Chapter3/ChatApp/src/client.nim | 54 ++++++++++ .../Chapter3/ChatApp/src/client.nim.cfg | 1 + .../niminaction/Chapter3/ChatApp/src/protocol.nim | 55 ++++++++++ tests/niminaction/Chapter3/ChatApp/src/server.nim | 84 +++++++++++++++ .../Chapter6/WikipediaStats/concurrency.nim | 79 ++++++++++++++ .../Chapter6/WikipediaStats/concurrency.nim.cfg | 1 + .../Chapter6/WikipediaStats/concurrency_regex.nim | 64 +++++++++++ .../WikipediaStats/concurrency_regex.nim.cfg | 1 + .../niminaction/Chapter6/WikipediaStats/naive.nim | 29 +++++ .../Chapter6/WikipediaStats/parallel_counts.nim | 72 +++++++++++++ .../WikipediaStats/parallel_counts.nim.cfg | 1 + .../Chapter6/WikipediaStats/race_condition.nim | 13 +++ .../Chapter6/WikipediaStats/race_condition.nim.cfg | 1 + .../Chapter6/WikipediaStats/sequential_counts.nim | 34 ++++++ .../Chapter6/WikipediaStats/unguarded_access.nim | 15 +++ .../WikipediaStats/unguarded_access.nim.cfg | 1 + tests/niminaction/Chapter7/Tweeter/Tweeter.nimble | 14 +++ .../niminaction/Chapter7/Tweeter/public/style.css | 117 +++++++++++++++++++++ .../Chapter7/Tweeter/src/createDatabase.nim | 6 ++ .../niminaction/Chapter7/Tweeter/src/database.nim | 93 ++++++++++++++++ tests/niminaction/Chapter7/Tweeter/src/tweeter.nim | 62 +++++++++++ .../Chapter7/Tweeter/src/views/general.nim | 51 +++++++++ .../Chapter7/Tweeter/src/views/user.nim | 49 +++++++++ .../Chapter7/Tweeter/tests/database_test.nim | 28 +++++ .../Chapter7/Tweeter/tests/database_test.nims | 2 + tests/niminaction/Chapter8/canvas/canvas.nim | 19 ++++ tests/niminaction/Chapter8/canvas/canvas_test.nim | 19 ++++ tests/niminaction/Chapter8/sdl/sdl.nim | 34 ++++++ tests/niminaction/Chapter8/sdl/sdl_test.nim | 25 +++++ tests/niminaction/Chapter8/sfml/sfml.nim | 26 +++++ tests/niminaction/Chapter8/sfml/sfml_test.nim | 9 ++ .../Chapter9/configurator/configurator.nim | 84 +++++++++++++++ tests/testament/categories.nim | 44 ++++++++ 34 files changed, 1213 insertions(+) create mode 100644 tests/niminaction/Chapter3/ChatApp/readme.markdown create mode 100644 tests/niminaction/Chapter3/ChatApp/src/client.nim create mode 100644 tests/niminaction/Chapter3/ChatApp/src/client.nim.cfg create mode 100644 tests/niminaction/Chapter3/ChatApp/src/protocol.nim create mode 100644 tests/niminaction/Chapter3/ChatApp/src/server.nim create mode 100644 tests/niminaction/Chapter6/WikipediaStats/concurrency.nim create mode 100644 tests/niminaction/Chapter6/WikipediaStats/concurrency.nim.cfg create mode 100644 tests/niminaction/Chapter6/WikipediaStats/concurrency_regex.nim create mode 100644 tests/niminaction/Chapter6/WikipediaStats/concurrency_regex.nim.cfg create mode 100644 tests/niminaction/Chapter6/WikipediaStats/naive.nim create mode 100644 tests/niminaction/Chapter6/WikipediaStats/parallel_counts.nim create mode 100644 tests/niminaction/Chapter6/WikipediaStats/parallel_counts.nim.cfg create mode 100644 tests/niminaction/Chapter6/WikipediaStats/race_condition.nim create mode 100644 tests/niminaction/Chapter6/WikipediaStats/race_condition.nim.cfg create mode 100644 tests/niminaction/Chapter6/WikipediaStats/sequential_counts.nim create mode 100644 tests/niminaction/Chapter6/WikipediaStats/unguarded_access.nim create mode 100644 tests/niminaction/Chapter6/WikipediaStats/unguarded_access.nim.cfg create mode 100644 tests/niminaction/Chapter7/Tweeter/Tweeter.nimble create mode 100644 tests/niminaction/Chapter7/Tweeter/public/style.css create mode 100644 tests/niminaction/Chapter7/Tweeter/src/createDatabase.nim create mode 100644 tests/niminaction/Chapter7/Tweeter/src/database.nim create mode 100644 tests/niminaction/Chapter7/Tweeter/src/tweeter.nim create mode 100644 tests/niminaction/Chapter7/Tweeter/src/views/general.nim create mode 100644 tests/niminaction/Chapter7/Tweeter/src/views/user.nim create mode 100644 tests/niminaction/Chapter7/Tweeter/tests/database_test.nim create mode 100644 tests/niminaction/Chapter7/Tweeter/tests/database_test.nims create mode 100644 tests/niminaction/Chapter8/canvas/canvas.nim create mode 100644 tests/niminaction/Chapter8/canvas/canvas_test.nim create mode 100644 tests/niminaction/Chapter8/sdl/sdl.nim create mode 100644 tests/niminaction/Chapter8/sdl/sdl_test.nim create mode 100644 tests/niminaction/Chapter8/sfml/sfml.nim create mode 100644 tests/niminaction/Chapter8/sfml/sfml_test.nim create mode 100644 tests/niminaction/Chapter9/configurator/configurator.nim (limited to 'tests') 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 .. 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 .. 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 .. 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 .. 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 .. = 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 .. + + + Tweeter written in Nim + + + + + ${body} + + + +#end proc +# +#proc renderLogin*(): string = +# result = "" +
+ Login + Please type in your username... +
+ + +
+
+#end proc +# +#proc renderTimeline*(username: string, messages: seq[Message]): string = +# result = "" +
+

${$!username}'s timeline

+
+
+ New message +
+ + + +
+
+${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 = "" +
+

${user.username}

+ Following: ${$user.following.len} +
+#end proc +# +#proc renderUser*(user: User, currentUser: User): string = +# result = "" +
+

${user.username}

+ Following: ${$user.following.len} + #if user.username notin currentUser.following: +
+ + + +
+ #end if +
+# +#end proc +# +#proc renderMessages*(messages: seq[Message]): string = +# result = "" +
+ #for message in messages: +
+ ${message.username} + ${message.time.getGMTime().format("HH:mm MMMM d',' yyyy")} +

${message.msg}

+
+ #end for +
+#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: "".} + +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. -- cgit 1.4.1-2-gfad0