forked from graphprotocol/graph-tooling
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest.ts
More file actions
329 lines (289 loc) · 9.89 KB
/
test.ts
File metadata and controls
329 lines (289 loc) · 9.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
import { exec } from 'child_process'
import os from 'os'
import path from 'path'
import { filesystem, patching, print, system } from 'gluegun'
import yaml from 'js-yaml'
import semver from 'semver'
import { Args, Command, Flags } from '@oclif/core'
import { GRAPH_CLI_SHARED_HEADERS } from '../constants'
import { spawn } from 'child_process'
export default class TestCommand extends Command {
static description = 'Runs rust binary for subgraph testing.';
static args = {
datasource: Args.string(),
};
static flags = {
help: Flags.help({
char: 'h',
}),
coverage: Flags.boolean({
summary: 'Run the tests in coverage mode.',
char: 'c',
}),
docker: Flags.boolean({
summary:
'Run the tests in a docker container (Note: Please execute from the root folder of the subgraph).',
char: 'd',
}),
force: Flags.boolean({
summary:
'Binary - overwrites folder + file when downloading. Docker - rebuilds the docker image.',
char: 'f',
}),
logs: Flags.boolean({
summary:
'Logs to the console information about the OS, CPU model and download url (debugging purposes).',
char: 'l',
}),
recompile: Flags.boolean({
summary: 'Force-recompile tests.',
char: 'r',
}),
version: Flags.string({
summary: 'Choose the version of the rust binary that you want to be downloaded/used.',
char: 'v',
}),
};
async run() {
const {
args: { datasource },
flags: { coverage, docker, force, logs, recompile, version },
} = await this.parse(TestCommand)
let testsDir = './tests'
// Check if matchstick.yaml config exists
if (filesystem.exists('matchstick.yaml')) {
try {
// Load the config
const config = await yaml.load(filesystem.read('matchstick.yaml', 'utf8')!)
// Check if matchstick.yaml and testsFolder not null
if (config?.testsFolder) {
// assign test folder from matchstick.yaml if present
testsDir = config.testsFolder
}
} catch (error) {
this.error(`A problem occurred while reading "matchstick.yaml":\n${error.message}`, {
exit: 1,
})
}
}
const cachePath = path.resolve(testsDir, '.latest.json')
const opts = {
testsDir,
cachePath,
coverage,
docker,
force,
logs,
recompile,
version,
}
// Fetch the latest version tag if version is not specified with -v/--version or if the version is not cached
if (opts.force || (!opts.version && !opts.latestVersion)) {
this.log('Fetching latest version tag...')
const result = await fetch(
'https://api.github.com/repos/LimeChain/matchstick/releases/latest',
{
headers: {
...GRAPH_CLI_SHARED_HEADERS,
},
},
)
const json = await result.json()
opts.latestVersion = json.tag_name
filesystem.file(opts.cachePath, {
content: {
version: json.tag_name,
timestamp: Date.now(),
},
})
}
if (opts.docker) {
runDocker.bind(this)(datasource, opts)
} else {
// Here, instead of downloading a binary, you compile the AssemblyScript to Wasm
// and then run it using the near-sdk-as runtime or a similar AssemblyScript runner.
compileAndRunAssemblyScript.bind(this)(datasource, opts)
}
}
}
async function compileAndRunAssemblyScript(
this: TestCommand,
datasource: string | undefined,
opts: {
// [include any relevant options]
},
) {
// Define paths to your AssemblyScript files and the output directory for the Wasm
const assemblyScriptEntry = 'path/to/matchstick-as/assembly/index.ts' // path to entry script
const outDir = './build' // directory to place compiled WebAssembly files
// Add necessary arguments for the AssemblyScript compiler
const ascArgs = [
assemblyScriptEntry,
'--binaryFile', `${outDir}/matchstick.wasm`,
// Add more compiler flags and arguments as needed
]
// Call the AssemblyScript compiler
const asc = spawn('asc', ascArgs, { stdio: 'inherit' })
asc.on('error', (error) => {
console.error(`Failed to compile AssemblyScript: ${error.message}`)
})
asc.on('close', (code) => {
if (code === 0) {
console.log(`AssemblyScript compiled successfully to ${outDir}/matchstick.wasm`)
// Here you would run the compiled Wasm using your chosen runtime
// This might be using `wasmer`, `wasmtime`, or another Wasm runtime
// For example, if using `wasmer`:
const runtime = spawn('wasmer', ['run', `${outDir}/matchstick.wasm`], { stdio: 'inherit' })
runtime.on('error', (error) => {
console.error(`Failed to execute WebAssembly: ${error.message}`)
})
runtime.on('close', (runtimeCode) => {
console.log(`WebAssembly execution finished with code ${runtimeCode}`)
})
} else {
console.error(`AssemblyScript compiler exited with code ${code}`)
}
})
}
/**
* The result of running `cat /etc/*-release | grep -E '(^VERSION|^NAME)='`
*
* May look like this:
* ```sh
* NAME="Ubuntu"
* VERSION="16.04.7 LTS (Xenial Xerus)"
* ```
*/
interface LinuxInfo {
name?: string
version?: string
}
async function getLinuxInfo(this: TestCommand): Promise<LinuxInfo> {
try {
const result = await system.run("cat /etc/*-release | grep -E '(^VERSION|^NAME)='", {
trim: true,
})
const infoArray = result
.replace(/['"]+/g, '')
.split('\n')
.map(p => p.split('='))
const linuxInfo: LinuxInfo = {}
for (const val of infoArray) {
linuxInfo[val[0].toLowerCase() as keyof LinuxInfo] = val[1]
}
return linuxInfo
} catch (error) {
this.error(`Error fetching the Linux version:\n${error}`, { exit: 1 })
}
}
async function runDocker(
this: TestCommand,
datasource: string | undefined,
opts: {
testsDir: string
coverage: boolean
force: boolean
version: string | undefined
latestVersion: string | null
recompile: boolean
},
) {
const coverageOpt = opts.coverage
const forceOpt = opts.force
const versionOpt = opts.version
const latestVersion = opts.latestVersion
const recompileOpt = opts.recompile
// Remove binary-install-raw binaries, because docker has permission issues
// when building the docker images
filesystem.remove('./node_modules/binary-install-raw/bin')
// Get current working directory
const current_folder = filesystem.cwd()
// Declate dockerfilePath with default location
const dockerfilePath = path.join(opts.testsDir, '.docker/Dockerfile')
// Check if the Dockerfil already exists
const dockerfileExists = filesystem.exists(dockerfilePath)
// Generate the Dockerfile only if it doesn't exists,
// version flag and/or force flag is passed.
if (!dockerfileExists || versionOpt || forceOpt) {
await dockerfile.bind(this)(dockerfilePath, versionOpt, latestVersion)
}
// Run a command to check if matchstick image already exists
exec('docker images -q matchstick', (_error, stdout, _stderr) => {
// Collect all(if any) flags and options that have to be passed to the matchstick binary
let testArgs = ''
if (coverageOpt) testArgs = testArgs + ' -c'
if (recompileOpt) testArgs = testArgs + ' -r'
if (datasource) testArgs = testArgs + ' ' + datasource
// Build the `docker run` command options and flags
const dockerRunOpts = [
'run',
'-it',
'--rm',
'--mount',
`type=bind,source=${current_folder},target=/matchstick`,
]
if (testArgs !== '') {
dockerRunOpts.push('-e')
dockerRunOpts.push(`ARGS=${testArgs.trim()}`)
}
dockerRunOpts.push('matchstick')
// If a matchstick image does not exists, the command returns an empty string,
// else it'll return the image ID. Skip `docker build` if an image already exists
// Delete current image(if any) and rebuild.
// Use spawn() and {stdio: 'inherit'} so we can see the logs in real time.
if (!dockerfileExists || stdout === '' || versionOpt || forceOpt) {
if (stdout !== '') {
exec('docker image rm matchstick', (_error, stdout, _stderr) => {
this.log(`Remove matchstick image result:\n${stdout}`)
})
}
// Build a docker image. If the process has executed successfully
// run a container from that image.
spawn('docker', ['build', '-f', dockerfilePath, '-t', 'matchstick', '.'], {
stdio: 'inherit',
}).on('close', code => {
if (code === 0) {
spawn('docker', dockerRunOpts, { stdio: 'inherit' })
}
})
} else {
this.log('Docker image already exists. Skipping `docker build` command...')
// Run the container from the existing matchstick docker image
spawn('docker', dockerRunOpts, { stdio: 'inherit' })
}
})
}
// Downloads Dockerfile template from the demo-subgraph repo
// Replaces the placeholders with their respective values
async function dockerfile(
this: TestCommand,
dockerfilePath: string,
versionOpt: string | null | undefined,
latestVersion: string | null | undefined,
) {
const spinner = print.spin('Generating Dockerfile...')
try {
// Fetch the Dockerfile template content from the demo-subgraph repo
const content = await fetch(
'https://raw.githubusercontent.com/LimeChain/demo-subgraph/main/Dockerfile',
).then(response => {
if (response.ok) {
return response.text()
}
throw new Error(`Status Code: ${response.status}, with error: ${response.statusText}`)
})
// Write the Dockerfile
filesystem.write(dockerfilePath, content)
// Replaces the version placeholders
await patching.replace(
dockerfilePath,
'<MATCHSTICK_VERSION>',
versionOpt || latestVersion || 'unknown',
)
} catch (error) {
this.error(`A problem occurred while generating the Dockerfile:\n${error.message}`, {
exit: 1,
})
}
spinner.succeed('Successfully generated Dockerfile.')
}