1 module testutils;
2 
3 version (unittest) {
4   import memgraph;
5 
6   // Port where memgraph is listening (running inside the docker container).
7   enum MEMGRAPH_PORT = 7688;
8 
9   bool canConnect() {
10     import std.string, std.conv;
11     auto params = mg_session_params_make();
12     assert(params != null);
13 
14     mg_session_params_set_host(params, toStringz("127.0.0.1"));
15     mg_session_params_set_port(params, to!ushort(MEMGRAPH_PORT));
16     mg_session_params_set_sslmode(params, mg_sslmode.MG_SSLMODE_DISABLE);
17 
18     // The mg_connect() will cause "mg_raw_transport_recv: Connection reset by peer" to be
19     // printed to stderr if the test docker container is not running. Maybe use freopen()
20     // to redirect stderr to /dev/null?
21     mg_session *session = null;
22     immutable status = mg_connect(params, &session);
23     mg_session_params_destroy(params);
24 
25     mg_session_destroy(session);
26 
27     return status == 0;
28   }  // canConnect()
29 
30   // Stop a unit testing memgraph container if it is running.
31   // Read the container id from $TMP/memgraph-d.container.
32   void stopContainer() {
33     import std.process, std.stdio, std.file, std.string;
34 
35     auto containerIdFileName = environment.get("TMP", "/tmp") ~ "/memgraph-d.container";
36     if (exists(containerIdFileName)) {
37       // Read container id from temp storage.
38       auto containerIdFile = File(containerIdFileName, "r");
39       auto containerId = containerIdFile.readln();
40       containerIdFile.close();
41       execute(["docker", "kill", containerId.chomp]);
42     }
43   }
44 
45   // Start a memgraph container for unit testing if it is not already running.
46   // Store the container id in $TMP/memgraph-d.container so it can be used in
47   // other tests without having to start a new container each time.
48   void startContainer() {
49     import std.process, std.stdio, std.file, std.string;
50 
51     auto containerIdFileName = environment.get("TMP", "/tmp") ~ "/memgraph-d.container";
52 
53     auto startContainer = true;
54 
55     if (exists(containerIdFileName)) {
56       // Read container id from temp storage.
57       auto containerIdFile = File(containerIdFileName, "r");
58       auto containerId = containerIdFile.readln();
59       containerIdFile.close();
60 
61       // Check if the container is still up and running.
62       auto ps = execute(["docker", "ps", "-q", "--no-trunc"]);
63       assert(ps.status == 0);
64       startContainer = false;
65 
66       if (ps.output.indexOf(containerId) < 0)
67         startContainer = true;
68     }
69 
70     if (startContainer) {
71       import std.conv;
72 
73       // Pull the latest memgraph docker image.
74       immutable pull = execute(["docker", "pull", "memgraph/memgraph"]);
75       assert(pull.status == 0);
76 
77       // Start a new memgraph docker container.
78       auto containerIdFile = File(containerIdFileName, "w");
79       immutable run = execute(["docker", "run", "-d", "-p",
80                 to!string(MEMGRAPH_PORT) ~ ":7687", "-d", "memgraph/memgraph"]);
81       assert(run.status == 0);
82 
83       // Store container id.
84       auto containerId = run.output;
85       containerIdFile.write(containerId);
86       containerIdFile.close();
87 
88       // Need to wait a while until the container is spun up, otherwise connecting will fail.
89       while (!canConnect()) {
90         import core.thread.osthread, core.time;
91         Thread.sleep(dur!("msecs")(250));
92       }
93     }
94   }  // startContainer()
95 
96   // Create a client connection to the running unit test container.
97   auto connectContainer() {
98     startContainer(); // Make sure container is up.
99     Params params;
100     params.port = MEMGRAPH_PORT;
101     return Client.connect(params);
102   }  // connectContainer()
103 
104   // Create an index on the test data.
105   void createTestIndex(ref Client client) {
106     assert(client.run("CREATE INDEX ON :Person(id);"), client.error);
107   }  // createTestIndex()
108 
109   // Delete the test data.
110   void deleteTestData(ref Client client) {
111     assert(client.run("MATCH (n) DETACH DELETE n;"), client.error);
112   }  // deleteTestData()
113 
114   // Create some test data.
115   void createTestData(ref Client client) {
116     // Create a few nodes.
117     assert(client.run(
118       "CREATE (:Person:Entrepreneur {id: 0, age: 40, name: 'John', " ~
119         "isStudent: false, score: 5.0});"), client.error);
120     assert(client.run(
121       "CREATE (:Person:Entrepreneur {id: 1, age: 20, name: 'Valery', " ~
122         "isStudent: true, score: 5.0});"), client.error);
123     assert(client.run(
124       "CREATE (:Person:Entrepreneur {id: 2, age: 50, name: 'Peter', " ~
125         "isStudent: false, score: 4.0});"), client.error);
126     assert(client.run(
127       "CREATE (:Person:Entrepreneur {id: 3, age: 30, name: 'Ray', " ~
128         "isStudent: false, score: 9.0});"), client.error);
129     assert(client.run(
130       "CREATE (:Person:Entrepreneur {id: 4, age: 25, name: 'Oløf', " ~
131         "isStudent: true, score: 10.0});"), client.error);
132 
133     // Create some relationships.
134     assert(client.run(
135       "MATCH (a:Person), (b:Person) WHERE a.name = 'John' AND b.name = 'Peter' " ~
136         "CREATE (a)-[r:IS_MANAGER]->(b);"), client.error);
137     assert(client.run(
138       "MATCH (a:Person), (b:Person) WHERE a.name = 'Peter' AND b.name = 'Valery' " ~
139         "CREATE (a)-[r:IS_MANAGER]->(b);"), client.error);
140     assert(client.run(
141       "MATCH (a:Person), (b:Person) WHERE a.name = 'Valery' AND b.name = 'Oløf' " ~
142         "CREATE (a)-[r:IS_MANAGER]->(b);"), client.error);
143   }  // createTestData()
144 
145 }
146 
147 unittest {
148   stopContainer();
149   assert(canConnect() == false);
150   startContainer();
151   assert(canConnect() == true);
152 }