@@ -2,70 +2,134 @@ const { Binary } = require('binary-install-raw')
2
2
const os = require ( 'os' )
3
3
const chalk = require ( 'chalk' )
4
4
const fetch = require ( 'node-fetch' )
5
+ const { filesystem, print } = require ( 'gluegun' )
6
+ const { fixParameters } = require ( '../command-helpers/gluegun' )
5
7
const semver = require ( 'semver' )
8
+ const { spawn, exec } = require ( 'child_process' )
9
+ const yaml = require ( 'js-yaml' )
6
10
7
11
const HELP = `
8
12
${ chalk . bold ( 'graph test' ) } ${ chalk . dim ( '[options]' ) } ${ chalk . bold ( '<datasource>' ) }
9
13
10
14
${ chalk . dim ( 'Options:' ) }
11
-
12
- -f --force Overwrite folder + file when downloading
15
+ -c, --coverage Run the tests in coverage mode. Works with v0.2.1 and above
16
+ -d, --docker Run the tests in a docker container(Note: Please execute from the root folder of the subgraph)
17
+ -f --force Binary - overwrites folder + file when downloading. Docker - rebuilds the docker image
13
18
-h, --help Show usage information
14
19
-l, --logs Logs to the console information about the OS, CPU model and download url (debugging purposes)
20
+ -r, --recompile Force-recompile tests (Not available in 0.2.2 and earlier versions)
15
21
-v, --version <tag> Choose the version of the rust binary that you want to be downloaded/used
16
22
`
17
23
18
24
module . exports = {
19
25
description : 'Runs rust binary for subgraph testing' ,
20
26
run : async toolbox => {
21
- // Obtain tools
22
- let { print } = toolbox
23
-
24
27
// Read CLI parameters
25
- let { f, force, h, help, l, logs, v, version } = toolbox . parameters . options
26
- let datasource = toolbox . parameters . first
28
+ let {
29
+ c,
30
+ coverage,
31
+ d,
32
+ docker,
33
+ f,
34
+ force,
35
+ h,
36
+ help,
37
+ l,
38
+ logs,
39
+ r,
40
+ recompile,
41
+ v,
42
+ version,
43
+ } = toolbox . parameters . options
27
44
45
+ let opts = new Map ( )
28
46
// Support both long and short option variants
29
- force = force || f
30
- help = help || h
31
- logs = logs || l
32
- version = version || v
47
+ opts . set ( "coverage" , coverage || c )
48
+ opts . set ( "docker" , docker || d )
49
+ opts . set ( "force" , force || f )
50
+ opts . set ( "help" , help || h )
51
+ opts . set ( "logs" , logs || l )
52
+ opts . set ( "recompile" , recompile || r )
53
+ opts . set ( "version" , version || v )
54
+
55
+ // Fix if a boolean flag (e.g -c, --coverage) has an argument
56
+ try {
57
+ fixParameters ( toolbox . parameters , {
58
+ h,
59
+ help,
60
+ c,
61
+ coverage,
62
+ d,
63
+ docker,
64
+ f,
65
+ force,
66
+ l,
67
+ logs,
68
+ r,
69
+ recompile
70
+ } )
71
+ } catch ( e ) {
72
+ print . error ( e . message )
73
+ process . exitCode = 1
74
+ return
75
+ }
76
+
77
+ let datasource = toolbox . parameters . first || toolbox . parameters . array [ 0 ]
33
78
34
79
// Show help text if requested
35
- if ( help ) {
80
+ if ( opts . get ( " help" ) ) {
36
81
print . info ( HELP )
37
82
return
38
83
}
39
84
40
- const platform = getPlatform ( logs )
41
- if ( ! version ) {
42
- let result = await fetch ( 'https://api.github.com/repos/LimeChain/matchstick/releases/latest' )
43
- let json = await result . json ( )
44
- version = json . tag_name
85
+ let result = await fetch ( 'https://api.github.com/repos/LimeChain/matchstick/releases/latest' )
86
+ let json = await result . json ( )
87
+ opts . set ( "latestVersion" , json . tag_name )
88
+
89
+ if ( opts . get ( "docker" ) ) {
90
+ runDocker ( datasource , opts )
91
+ } else {
92
+ runBinary ( datasource , opts )
45
93
}
94
+ }
95
+ }
46
96
47
- const url = `https://github.com/LimeChain/matchstick/releases/download/${ version } /${ platform } `
97
+ async function runBinary ( datasource , opts ) {
98
+ let coverageOpt = opts . get ( "coverage" )
99
+ let forceOpt = opts . get ( "force" )
100
+ let logsOpt = opts . get ( "logs" )
101
+ let versionOpt = opts . get ( "version" )
102
+ let latestVersion = opts . get ( "latestVersion" )
103
+ let recompileOpt = opts . get ( "recompile" )
48
104
49
- if ( logs ) {
50
- console . log ( `Download link: ${ url } ` )
51
- }
105
+ const platform = getPlatform ( logsOpt )
52
106
53
- let binary = new Binary ( platform , url , version )
54
- await binary . install ( force )
55
- datasource ? binary . run ( datasource ) : binary . run ( )
107
+ const url = `https://github.com/LimeChain/matchstick/releases/download/${ versionOpt || latestVersion } /${ platform } `
108
+
109
+ if ( logsOpt ) {
110
+ print . info ( `Download link: ${ url } ` )
56
111
}
112
+
113
+ let binary = new Binary ( platform , url , versionOpt || latestVersion )
114
+ forceOpt ? await binary . install ( true ) : await binary . install ( false )
115
+ let args = new Array ( )
116
+
117
+ if ( coverageOpt ) args . push ( '-c' )
118
+ if ( recompileOpt ) args . push ( '-r' )
119
+ if ( datasource ) args . push ( datasource )
120
+ args . length > 0 ? binary . run ( ...args ) : binary . run ( )
57
121
}
58
122
59
- function getPlatform ( logs ) {
123
+ function getPlatform ( logsOpt ) {
60
124
const type = os . type ( )
61
125
const arch = os . arch ( )
62
126
const release = os . release ( )
63
127
const cpuCore = os . cpus ( ) [ 0 ]
64
128
const majorVersion = semver . major ( release )
65
129
const isM1 = cpuCore . model . includes ( "Apple M1" )
66
130
67
- if ( logs ) {
68
- console . log ( `OS type: ${ type } \nOS arch: ${ arch } \nOS release: ${ release } \nOS major version: ${ majorVersion } \nCPU model: ${ cpuCore . model } ` )
131
+ if ( logsOpt ) {
132
+ print . info ( `OS type: ${ type } \nOS arch: ${ arch } \nOS release: ${ release } \nOS major version: ${ majorVersion } \nCPU model: ${ cpuCore . model } ` )
69
133
}
70
134
71
135
if ( arch === 'x64' || ( arch === 'arm64' && isM1 ) ) {
@@ -90,3 +154,122 @@ function getPlatform(logs) {
90
154
91
155
throw new Error ( `Unsupported platform: ${ type } ${ arch } ${ majorVersion } ` )
92
156
}
157
+
158
+ async function runDocker ( datasource , opts ) {
159
+ let coverageOpt = opts . get ( "coverage" )
160
+ let forceOpt = opts . get ( "force" )
161
+ let versionOpt = opts . get ( "version" )
162
+ let latestVersion = opts . get ( "latestVersion" )
163
+ let recompileOpt = opts . get ( "recompile" )
164
+
165
+ // Remove binary-install-raw binaries, because docker has permission issues
166
+ // when building the docker images
167
+ await filesystem . remove ( "./node_modules/binary-install-raw/bin" )
168
+
169
+ // Get current working directory
170
+ let current_folder = await filesystem . cwd ( )
171
+
172
+ // Build the Dockerfile location. Defaults to ./tests/.docker if
173
+ // a custom testsFolder is not declared in the subgraph.yaml
174
+ let dockerDir = ""
175
+
176
+ try {
177
+ let doc = await yaml . load ( filesystem . read ( 'subgraph.yaml' , 'utf8' ) )
178
+ testsFolder = doc . testsFolder || './tests'
179
+ dockerDir = testsFolder . endsWith ( '/' ) ? testsFolder + '.docker' : testsFolder + '/.docker'
180
+ } catch ( error ) {
181
+ print . error ( error . message )
182
+ return
183
+ }
184
+
185
+ // Create the Dockerfile
186
+ try {
187
+ await filesystem . write ( `${ dockerDir } /Dockerfile` , dockerfile ( versionOpt , latestVersion ) )
188
+ print . info ( 'Successfully generated Dockerfile.' )
189
+ } catch ( error ) {
190
+ print . info ( 'A problem occurred while generating the Dockerfile. Please attend to the errors below:' )
191
+ print . error ( error . message )
192
+ return
193
+ }
194
+
195
+ // Run a command to check if matchstick image already exists
196
+ exec ( 'docker images -q matchstick' , ( error , stdout , stderr ) => {
197
+ // Collect all(if any) flags and options that have to be passed to the matchstick binary
198
+ let testArgs = ''
199
+ if ( coverageOpt ) testArgs = testArgs + ' -c'
200
+ if ( recompileOpt ) testArgs = testArgs + ' -r'
201
+ if ( datasource ) testArgs = testArgs + ' ' + datasource
202
+
203
+ // Build the `docker run` command options and flags
204
+ let dockerRunOpts = [ 'run' , '-it' , '--rm' , '--mount' , `type=bind,source=${ current_folder } ,target=/matchstick` ]
205
+
206
+ if ( testArgs !== '' ) {
207
+ dockerRunOpts . push ( '-e' )
208
+ dockerRunOpts . push ( `ARGS=${ testArgs . trim ( ) } ` )
209
+ }
210
+
211
+ dockerRunOpts . push ( 'matchstick' )
212
+
213
+ // If a matchstick image does not exists, the command returns an empty string,
214
+ // else it'll return the image ID. Skip `docker build` if an image already exists
215
+ // If `-v/--version` is specified, delete current image(if any) and rebuild.
216
+ // Use spawn() and {stdio: 'inherit'} so we can see the logs in real time.
217
+ if ( stdout === '' || versionOpt || forceOpt ) {
218
+ if ( ( stdout !== '' && versionOpt ) || forceOpt ) {
219
+ exec ( 'docker image rm matchstick' , ( error , stdout , stderr ) => {
220
+ print . info ( chalk . bold ( `Removing matchstick image\n${ stdout } ` ) )
221
+ } )
222
+ }
223
+ // Build a docker image. If the process has executed successfully
224
+ // run a container from that image.
225
+ spawn (
226
+ 'docker' ,
227
+ [ 'build' , '--no-cache' , '-f' , `${ dockerDir } /Dockerfile` , '-t' , 'matchstick' , '.' ] ,
228
+ { stdio : 'inherit' }
229
+ ) . on ( 'close' , code => {
230
+ if ( code === 0 ) {
231
+ spawn ( 'docker' , dockerRunOpts , { stdio : 'inherit' } )
232
+ }
233
+ } )
234
+ } else {
235
+ print . info ( "Docker image already exists. Skipping `docker build` command." )
236
+ // Run the container from the existing matchstick docker image
237
+ spawn ( 'docker' , dockerRunOpts , { stdio : 'inherit' } )
238
+ }
239
+ } )
240
+ }
241
+
242
+ // TODO: Move these in separate file (in a function maybe)
243
+ function dockerfile ( versionOpt , latestVersion ) {
244
+ return `
245
+ FROM ubuntu:20.04
246
+ ENV ARGS=""
247
+
248
+ # Install necessary packages
249
+ RUN apt update
250
+ RUN apt install -y nodejs
251
+ RUN apt install -y npm
252
+ RUN apt install -y git
253
+ RUN apt install -y postgresql
254
+ RUN apt install -y curl
255
+ RUN apt install -y cmake
256
+ RUN npm install -g @graphprotocol/graph-cli
257
+
258
+ # Download the latest linux binary
259
+ RUN curl -OL https://github.com/LimeChain/matchstick/releases/download/${ versionOpt || latestVersion } /binary-linux-20
260
+
261
+ # Make it executable
262
+ RUN chmod a+x binary-linux-20
263
+
264
+ # Create a matchstick dir where the host will be copied
265
+ RUN mkdir matchstick
266
+ WORKDIR matchstick
267
+
268
+ # Copy host to /matchstick
269
+ COPY ../ .
270
+
271
+ RUN graph codegen
272
+ RUN graph build
273
+
274
+ CMD ../binary-linux-20 \${ARGS}`
275
+ }
0 commit comments