@5ive-tech/cli 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +226 -0
- package/dist/assets/vm/five_vm_wasm.d.ts +762 -0
- package/dist/assets/vm/five_vm_wasm.js +3754 -0
- package/dist/assets/vm/five_vm_wasm_bg.wasm +0 -0
- package/dist/assets/vm/five_vm_wasm_bg.wasm.d.ts +247 -0
- package/dist/assets/vm/package.json +11 -0
- package/dist/cli.d.ts +47 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +343 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/analyze.d.ts +3 -0
- package/dist/commands/analyze.d.ts.map +1 -0
- package/dist/commands/analyze.js +435 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/build.d.ts +3 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +66 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/compile.d.ts +3 -0
- package/dist/commands/compile.d.ts.map +1 -0
- package/dist/commands/compile.js +872 -0
- package/dist/commands/compile.js.map +1 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +431 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/deploy-and-execute.d.ts +3 -0
- package/dist/commands/deploy-and-execute.d.ts.map +1 -0
- package/dist/commands/deploy-and-execute.js +317 -0
- package/dist/commands/deploy-and-execute.js.map +1 -0
- package/dist/commands/deploy.d.ts +21 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +806 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/donate.d.ts +4 -0
- package/dist/commands/donate.d.ts.map +1 -0
- package/dist/commands/donate.js +104 -0
- package/dist/commands/donate.js.map +1 -0
- package/dist/commands/execute.d.ts +6 -0
- package/dist/commands/execute.d.ts.map +1 -0
- package/dist/commands/execute.js +749 -0
- package/dist/commands/execute.js.map +1 -0
- package/dist/commands/fmt.d.ts +3 -0
- package/dist/commands/fmt.d.ts.map +1 -0
- package/dist/commands/fmt.js +327 -0
- package/dist/commands/fmt.js.map +1 -0
- package/dist/commands/help.d.ts +6 -0
- package/dist/commands/help.d.ts.map +1 -0
- package/dist/commands/help.js +224 -0
- package/dist/commands/help.js.map +1 -0
- package/dist/commands/index.d.ts +45 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +119 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +887 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/local.d.ts +3 -0
- package/dist/commands/local.d.ts.map +1 -0
- package/dist/commands/local.js +703 -0
- package/dist/commands/local.js.map +1 -0
- package/dist/commands/namespace.d.ts +3 -0
- package/dist/commands/namespace.d.ts.map +1 -0
- package/dist/commands/namespace.js +328 -0
- package/dist/commands/namespace.js.map +1 -0
- package/dist/commands/template.d.ts +4 -0
- package/dist/commands/template.d.ts.map +1 -0
- package/dist/commands/template.js +486 -0
- package/dist/commands/template.js.map +1 -0
- package/dist/commands/test.d.ts +6 -0
- package/dist/commands/test.d.ts.map +1 -0
- package/dist/commands/test.js +890 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/commands/version.d.ts +6 -0
- package/dist/commands/version.d.ts.map +1 -0
- package/dist/commands/version.js +339 -0
- package/dist/commands/version.js.map +1 -0
- package/dist/config/ConfigManager.d.ts +69 -0
- package/dist/config/ConfigManager.d.ts.map +1 -0
- package/dist/config/ConfigManager.js +261 -0
- package/dist/config/ConfigManager.js.map +1 -0
- package/dist/config/index.d.ts +10 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +21 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/types.d.ts +35 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +105 -0
- package/dist/config/types.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/project/ProjectLoader.d.ts +12 -0
- package/dist/project/ProjectLoader.d.ts.map +1 -0
- package/dist/project/ProjectLoader.js +115 -0
- package/dist/project/ProjectLoader.js.map +1 -0
- package/dist/types.d.ts +334 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/AccountFixtureGenerator.d.ts +48 -0
- package/dist/utils/AccountFixtureGenerator.d.ts.map +1 -0
- package/dist/utils/AccountFixtureGenerator.js +265 -0
- package/dist/utils/AccountFixtureGenerator.js.map +1 -0
- package/dist/utils/FiveFileManager.d.ts +96 -0
- package/dist/utils/FiveFileManager.d.ts.map +1 -0
- package/dist/utils/FiveFileManager.js +329 -0
- package/dist/utils/FiveFileManager.js.map +1 -0
- package/dist/utils/ascii-art.d.ts +72 -0
- package/dist/utils/ascii-art.d.ts.map +1 -0
- package/dist/utils/ascii-art.js +314 -0
- package/dist/utils/ascii-art.js.map +1 -0
- package/dist/utils/cli-ui.d.ts +39 -0
- package/dist/utils/cli-ui.d.ts.map +1 -0
- package/dist/utils/cli-ui.js +75 -0
- package/dist/utils/cli-ui.js.map +1 -0
- package/dist/utils/fileUtils.d.ts +25 -0
- package/dist/utils/fileUtils.d.ts.map +1 -0
- package/dist/utils/fileUtils.js +50 -0
- package/dist/utils/fileUtils.js.map +1 -0
- package/dist/utils/logger.d.ts +53 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +287 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/wasm/compiler.d.ts +101 -0
- package/dist/wasm/compiler.d.ts.map +1 -0
- package/dist/wasm/compiler.js +906 -0
- package/dist/wasm/compiler.js.map +1 -0
- package/dist/wasm/loader.d.ts +2 -0
- package/dist/wasm/loader.d.ts.map +1 -0
- package/dist/wasm/loader.js +90 -0
- package/dist/wasm/loader.js.map +1 -0
- package/dist/wasm/vm.d.ts +32 -0
- package/dist/wasm/vm.d.ts.map +1 -0
- package/dist/wasm/vm.js +440 -0
- package/dist/wasm/vm.js.map +1 -0
- package/package.json +100 -0
|
@@ -0,0 +1,890 @@
|
|
|
1
|
+
// Test command.
|
|
2
|
+
import { readFile, readdir, stat } from 'fs/promises';
|
|
3
|
+
import { join, basename, isAbsolute } from 'path';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import { FiveSDK, FiveTestRunner, TestDiscovery } from '@5ive-tech/sdk';
|
|
6
|
+
import { ConfigManager } from '../config/ConfigManager.js';
|
|
7
|
+
import { Connection, Keypair } from '@solana/web3.js';
|
|
8
|
+
import { FiveFileManager } from '../utils/FiveFileManager.js';
|
|
9
|
+
import { loadBuildManifest, loadProjectConfig } from '../project/ProjectLoader.js';
|
|
10
|
+
import { section, success as uiSuccess, error as uiError } from '../utils/cli-ui.js';
|
|
11
|
+
/**
|
|
12
|
+
* Five test command implementation
|
|
13
|
+
*/
|
|
14
|
+
export const testCommand = {
|
|
15
|
+
name: 'test',
|
|
16
|
+
description: 'Run test suites',
|
|
17
|
+
aliases: ['t'],
|
|
18
|
+
options: [
|
|
19
|
+
{
|
|
20
|
+
flags: '-p, --pattern <pattern>',
|
|
21
|
+
description: 'Test file pattern (default: **/*.test.json)',
|
|
22
|
+
defaultValue: '**/*.test.json'
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
flags: '-f, --filter <filter>',
|
|
26
|
+
description: 'Run tests matching filter pattern',
|
|
27
|
+
required: false
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
flags: '--timeout <ms>',
|
|
31
|
+
description: 'Test timeout in milliseconds',
|
|
32
|
+
defaultValue: 30000
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
flags: '--max-cu <units>',
|
|
36
|
+
description: 'Maximum compute units per test',
|
|
37
|
+
defaultValue: 1000000
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
flags: '--parallel <count>',
|
|
41
|
+
description: 'Number of parallel test workers (0 = CPU count)',
|
|
42
|
+
defaultValue: 0
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
flags: '--benchmark',
|
|
46
|
+
description: 'Run performance benchmarks',
|
|
47
|
+
defaultValue: false
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
flags: '--coverage',
|
|
51
|
+
description: 'Generate test coverage report',
|
|
52
|
+
defaultValue: false
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
flags: '--watch',
|
|
56
|
+
description: 'Watch for file changes and re-run tests',
|
|
57
|
+
defaultValue: false
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
flags: '--format <format>',
|
|
61
|
+
description: 'Output format',
|
|
62
|
+
choices: ['text', 'json', 'junit'],
|
|
63
|
+
defaultValue: 'text'
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
flags: '--verbose',
|
|
67
|
+
description: 'Show detailed test output',
|
|
68
|
+
defaultValue: false
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
flags: '--sdk-runner',
|
|
72
|
+
description: 'Use modern SDK-based test runner (recommended)',
|
|
73
|
+
defaultValue: false
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
flags: '--on-chain',
|
|
77
|
+
description: 'Execute tests on-chain (deploy + execute)',
|
|
78
|
+
defaultValue: false
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
flags: '--batch',
|
|
82
|
+
description: 'Run all .bin files in batch mode',
|
|
83
|
+
defaultValue: false
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
flags: '-t, --target <target>',
|
|
87
|
+
description: 'Override target network (devnet, testnet, mainnet, local)',
|
|
88
|
+
required: false
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
flags: '-n, --network <url>',
|
|
92
|
+
description: 'Override network RPC URL',
|
|
93
|
+
required: false
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
flags: '-k, --keypair <file>',
|
|
97
|
+
description: 'Override keypair file path',
|
|
98
|
+
required: false
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
flags: '--retry-failed',
|
|
102
|
+
description: 'Retry only previously failed tests',
|
|
103
|
+
defaultValue: false
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
flags: '--analyze-costs',
|
|
107
|
+
description: 'Include detailed cost analysis in results',
|
|
108
|
+
defaultValue: false
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
flags: '--project <path>',
|
|
112
|
+
description: 'Project directory or five.toml path',
|
|
113
|
+
required: false
|
|
114
|
+
}
|
|
115
|
+
],
|
|
116
|
+
arguments: [
|
|
117
|
+
{
|
|
118
|
+
name: 'path',
|
|
119
|
+
description: 'Test directory or file (default: ./tests)',
|
|
120
|
+
required: false
|
|
121
|
+
}
|
|
122
|
+
],
|
|
123
|
+
examples: [
|
|
124
|
+
{
|
|
125
|
+
command: 'five test',
|
|
126
|
+
description: 'Run all tests in ./tests directory'
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
command: 'five test --filter "token*" --verbose',
|
|
130
|
+
description: 'Run token tests with verbose output'
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
command: 'five test ./my-tests --benchmark --format json',
|
|
134
|
+
description: 'Run benchmarks with JSON output'
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
command: 'five test --watch --parallel 4',
|
|
138
|
+
description: 'Watch mode with 4 parallel workers'
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
command: 'five test test-scripts/ --on-chain --target devnet',
|
|
142
|
+
description: 'Run on-chain tests on devnet'
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
command: 'five test test-scripts/ --on-chain --batch --analyze-costs',
|
|
146
|
+
description: 'Batch test all .bin files with cost analysis'
|
|
147
|
+
}
|
|
148
|
+
],
|
|
149
|
+
handler: async (args, options, context) => {
|
|
150
|
+
const { logger } = context;
|
|
151
|
+
try {
|
|
152
|
+
const projectContext = await loadProjectConfig(options.project, process.cwd());
|
|
153
|
+
const manifest = projectContext ? await loadBuildManifest(projectContext.rootDir) : null;
|
|
154
|
+
// Apply project defaults if not provided
|
|
155
|
+
if (!options.target && projectContext?.config.cluster) {
|
|
156
|
+
options.target = projectContext.config.cluster;
|
|
157
|
+
}
|
|
158
|
+
if (!options.network && projectContext?.config.rpcUrl) {
|
|
159
|
+
options.network = projectContext.config.rpcUrl;
|
|
160
|
+
}
|
|
161
|
+
if (!options.keypair && projectContext?.config.keypairPath) {
|
|
162
|
+
options.keypair = projectContext.config.keypairPath;
|
|
163
|
+
}
|
|
164
|
+
let testPath = args[0] ||
|
|
165
|
+
(projectContext ? join(projectContext.rootDir, 'tests') : undefined) ||
|
|
166
|
+
'./tests';
|
|
167
|
+
if (!args[0] && manifest?.artifact_path) {
|
|
168
|
+
testPath = isAbsolute(manifest.artifact_path)
|
|
169
|
+
? manifest.artifact_path
|
|
170
|
+
: projectContext
|
|
171
|
+
? join(projectContext.rootDir, manifest.artifact_path)
|
|
172
|
+
: manifest.artifact_path;
|
|
173
|
+
}
|
|
174
|
+
// Handle on-chain testing mode
|
|
175
|
+
if (options.onChain) {
|
|
176
|
+
await runOnChainTests(testPath, options, context);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
// Use modern SDK-based test runner if requested
|
|
180
|
+
if (options.sdkRunner) {
|
|
181
|
+
await runWithSdkRunner(testPath, options, context);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
// Legacy approach with SDK integration
|
|
185
|
+
// Initialize SDK for testing
|
|
186
|
+
const spinner = ora('Initializing Five SDK for testing...').start();
|
|
187
|
+
// No initialization needed for SDK - it's stateless
|
|
188
|
+
const sdk = FiveSDK.create({ debug: options.verbose });
|
|
189
|
+
spinner.succeed('Five SDK initialized');
|
|
190
|
+
// Discover test files
|
|
191
|
+
const testSuites = await discoverTestSuites(testPath, options, logger);
|
|
192
|
+
if (testSuites.length === 0) {
|
|
193
|
+
logger.warn('No test files found');
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
logger.info(`Found ${testSuites.length} test suite(s) with ${getTotalTestCount(testSuites)} test(s)`);
|
|
197
|
+
// Run tests
|
|
198
|
+
const results = await runTestSuites(testSuites, sdk, options, context);
|
|
199
|
+
// Display results
|
|
200
|
+
displayTestResults(results, options, logger);
|
|
201
|
+
// Handle watch mode
|
|
202
|
+
if (options.watch) {
|
|
203
|
+
await watchAndRerun(testPath, options, context);
|
|
204
|
+
}
|
|
205
|
+
// Exit with appropriate code
|
|
206
|
+
const failed = results.some(suite => suite.results.some(test => !test.passed));
|
|
207
|
+
if (failed) {
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
logger.error('Test execution failed:', error);
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
/**
|
|
218
|
+
* Discover test suites from files (both .test.json and .v source)
|
|
219
|
+
*/
|
|
220
|
+
async function discoverTestSuites(testPath, options, logger) {
|
|
221
|
+
const testSuites = [];
|
|
222
|
+
const compiledVTests = new Map();
|
|
223
|
+
try {
|
|
224
|
+
// Use new TestDiscovery to find both .test.json and .v files
|
|
225
|
+
const discoveredTests = await TestDiscovery.discoverTests(testPath, { verbose: options.verbose });
|
|
226
|
+
if (discoveredTests.length === 0 && options.verbose) {
|
|
227
|
+
logger.info('No tests discovered');
|
|
228
|
+
}
|
|
229
|
+
// Organize discovered tests into suites
|
|
230
|
+
const suiteMap = new Map();
|
|
231
|
+
for (const test of discoveredTests) {
|
|
232
|
+
if (test.type === 'v-source') {
|
|
233
|
+
// Compile .v source file if not already compiled
|
|
234
|
+
if (!compiledVTests.has(test.path)) {
|
|
235
|
+
const spinner = ora(`Compiling ${basename(test.path)}...`).start();
|
|
236
|
+
try {
|
|
237
|
+
const compilation = await TestDiscovery.compileVTest(test.path);
|
|
238
|
+
if (compilation.success && compilation.bytecode) {
|
|
239
|
+
compiledVTests.set(test.path, compilation.bytecode);
|
|
240
|
+
spinner.succeed(`Compiled ${basename(test.path)}`);
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
spinner.fail(`Failed to compile ${basename(test.path)}`);
|
|
244
|
+
logger.error(`Compilation errors: ${compilation.errors?.join(', ')}`);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
spinner.fail(`Error compiling ${basename(test.path)}`);
|
|
250
|
+
logger.error(error instanceof Error ? error.message : 'Unknown error');
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Create test case from compiled .v file
|
|
255
|
+
const bytecode = compiledVTests.get(test.path);
|
|
256
|
+
if (bytecode) {
|
|
257
|
+
// Write bytecode to temp location
|
|
258
|
+
const fs = await import('fs/promises');
|
|
259
|
+
const tmpDir = join(process.cwd(), '.five', 'test-cache');
|
|
260
|
+
await fs.mkdir(tmpDir, { recursive: true });
|
|
261
|
+
const bytecodeFile = join(tmpDir, `${test.name.replace(/:/g, '_')}.bin`);
|
|
262
|
+
await fs.writeFile(bytecodeFile, bytecode);
|
|
263
|
+
const suite = suiteMap.get(test.path) || [];
|
|
264
|
+
suite.push({
|
|
265
|
+
name: test.name,
|
|
266
|
+
bytecode: bytecodeFile,
|
|
267
|
+
input: undefined,
|
|
268
|
+
accounts: undefined,
|
|
269
|
+
expected: {
|
|
270
|
+
success: true,
|
|
271
|
+
result: test.parameters ? test.parameters[test.parameters.length - 1] : undefined
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
suiteMap.set(test.path, suite);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
else if (test.type === 'json-suite') {
|
|
278
|
+
// Load .test.json suite
|
|
279
|
+
const suite = await loadTestSuite(test.path);
|
|
280
|
+
if (suite) {
|
|
281
|
+
testSuites.push(suite);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// Convert compiled V tests to TestSuite format
|
|
286
|
+
for (const [filePath, testCases] of suiteMap.entries()) {
|
|
287
|
+
testSuites.push({
|
|
288
|
+
name: `${basename(filePath, '.v')}`,
|
|
289
|
+
description: `Tests from ${filePath}`,
|
|
290
|
+
testCases
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
logger.warn(`Failed to discover tests at ${testPath}: ${error}`);
|
|
296
|
+
}
|
|
297
|
+
// Apply filter if specified
|
|
298
|
+
if (options.filter) {
|
|
299
|
+
return testSuites.filter(suite => suite.name.includes(options.filter) ||
|
|
300
|
+
suite.testCases.some(test => test.name.includes(options.filter)));
|
|
301
|
+
}
|
|
302
|
+
return testSuites;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Load test suite from JSON file
|
|
306
|
+
*/
|
|
307
|
+
async function loadTestSuite(filePath) {
|
|
308
|
+
try {
|
|
309
|
+
const content = await readFile(filePath, 'utf8');
|
|
310
|
+
const data = JSON.parse(content);
|
|
311
|
+
return {
|
|
312
|
+
name: data.name || basename(filePath, '.test.json'),
|
|
313
|
+
description: data.description,
|
|
314
|
+
testCases: data.tests || data.testCases || []
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
console.warn(`Failed to load test suite ${filePath}: ${error}`);
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Run all test suites
|
|
324
|
+
*/
|
|
325
|
+
async function runTestSuites(testSuites, sdk, options, context) {
|
|
326
|
+
const { logger } = context;
|
|
327
|
+
const results = [];
|
|
328
|
+
for (const suite of testSuites) {
|
|
329
|
+
logger.info(`Running test suite: ${suite.name}`);
|
|
330
|
+
const suiteResults = [];
|
|
331
|
+
for (const testCase of suite.testCases) {
|
|
332
|
+
const result = await runSingleTest(testCase, sdk, options, context);
|
|
333
|
+
suiteResults.push(result);
|
|
334
|
+
if (options.verbose) {
|
|
335
|
+
displaySingleTestResult(result, logger);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
results.push({ suite, results: suiteResults });
|
|
339
|
+
}
|
|
340
|
+
return results;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Run a single test case
|
|
344
|
+
*/
|
|
345
|
+
async function runSingleTest(testCase, sdk, options, context) {
|
|
346
|
+
const { logger } = context;
|
|
347
|
+
const startTime = Date.now();
|
|
348
|
+
try {
|
|
349
|
+
// Load bytecode using centralized manager
|
|
350
|
+
const fileManager = FiveFileManager.getInstance();
|
|
351
|
+
const loadedFile = await fileManager.loadFile(testCase.bytecode, {
|
|
352
|
+
validateFormat: true
|
|
353
|
+
});
|
|
354
|
+
const bytecode = loadedFile.bytecode;
|
|
355
|
+
// Validation already done by file manager with validateFormat: true
|
|
356
|
+
const validation = { valid: true }; // Skip redundant validation
|
|
357
|
+
// Validation handled by centralized file manager
|
|
358
|
+
// Parse input parameters if specified
|
|
359
|
+
let parameters = [];
|
|
360
|
+
if (testCase.input) {
|
|
361
|
+
const inputData = await readFile(testCase.input, 'utf8');
|
|
362
|
+
try {
|
|
363
|
+
parameters = JSON.parse(inputData);
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
// If not JSON, treat as raw string parameter
|
|
367
|
+
parameters = [inputData];
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
// Execute with timeout using Five SDK
|
|
371
|
+
const executionPromise = FiveSDK.executeLocally(bytecode, 0, // Default to first function
|
|
372
|
+
parameters, {
|
|
373
|
+
debug: options.verbose,
|
|
374
|
+
trace: options.verbose,
|
|
375
|
+
computeUnitLimit: options.maxCu,
|
|
376
|
+
abi: loadedFile.abi // Pass ABI for function name resolution
|
|
377
|
+
});
|
|
378
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Test timeout')), options.timeout));
|
|
379
|
+
const result = await Promise.race([executionPromise, timeoutPromise]);
|
|
380
|
+
const duration = Date.now() - startTime;
|
|
381
|
+
// Validate result against expected
|
|
382
|
+
const passed = validateTestResult(result, testCase.expected);
|
|
383
|
+
return {
|
|
384
|
+
name: testCase.name,
|
|
385
|
+
passed,
|
|
386
|
+
duration,
|
|
387
|
+
computeUnits: result.computeUnitsUsed || 0,
|
|
388
|
+
details: options.verbose ? result : undefined
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
const duration = Date.now() - startTime;
|
|
393
|
+
// Check if error was expected
|
|
394
|
+
const passed = testCase.expected.success === false &&
|
|
395
|
+
testCase.expected.error !== undefined &&
|
|
396
|
+
error instanceof Error &&
|
|
397
|
+
error.message.includes(testCase.expected.error);
|
|
398
|
+
return {
|
|
399
|
+
name: testCase.name,
|
|
400
|
+
passed,
|
|
401
|
+
duration,
|
|
402
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Validate test result against expected outcome
|
|
408
|
+
*/
|
|
409
|
+
function validateTestResult(result, expected) {
|
|
410
|
+
// Check success/failure
|
|
411
|
+
if (result.success !== expected.success) {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
// If expecting success, check result value
|
|
415
|
+
if (expected.success && expected.result !== undefined) {
|
|
416
|
+
if (JSON.stringify(result.result) !== JSON.stringify(expected.result)) {
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
// Check compute units limit
|
|
421
|
+
if (expected.maxComputeUnits && result.computeUnitsUsed > expected.maxComputeUnits) {
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Display single test result
|
|
428
|
+
*/
|
|
429
|
+
function displaySingleTestResult(result, logger) {
|
|
430
|
+
const status = result.passed ? 'OK' : 'FAIL';
|
|
431
|
+
const duration = `(${result.duration}ms)`;
|
|
432
|
+
const cu = result.computeUnits ? `[${result.computeUnits} CU]` : '';
|
|
433
|
+
console.log(` ${status} ${result.name} ${duration} ${cu}`);
|
|
434
|
+
if (!result.passed && result.error) {
|
|
435
|
+
console.log(` Error: ${result.error}`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Display comprehensive test results
|
|
440
|
+
*/
|
|
441
|
+
function displayTestResults(results, options, logger) {
|
|
442
|
+
if (options.format === 'json') {
|
|
443
|
+
console.log(JSON.stringify(results, null, 2));
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
console.log('\n' + section('Test Results'));
|
|
447
|
+
let totalTests = 0;
|
|
448
|
+
let totalPassed = 0;
|
|
449
|
+
let totalDuration = 0;
|
|
450
|
+
for (const { suite, results: suiteResults } of results) {
|
|
451
|
+
const passed = suiteResults.filter(r => r.passed).length;
|
|
452
|
+
const total = suiteResults.length;
|
|
453
|
+
const suiteDuration = suiteResults.reduce((sum, r) => sum + r.duration, 0);
|
|
454
|
+
totalTests += total;
|
|
455
|
+
totalPassed += passed;
|
|
456
|
+
totalDuration += suiteDuration;
|
|
457
|
+
const status = passed === total ? 'OK' : 'FAIL';
|
|
458
|
+
console.log(`\n${status} ${suite.name}: ${passed}/${total} passed (${suiteDuration}ms)`);
|
|
459
|
+
if (options.verbose || passed !== total) {
|
|
460
|
+
suiteResults.forEach(result => displaySingleTestResult(result, logger));
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
// Summary
|
|
464
|
+
console.log('\n' + section('Summary'));
|
|
465
|
+
console.log(` Total: ${totalTests} tests`);
|
|
466
|
+
console.log(` Passed: ${totalPassed}`);
|
|
467
|
+
const failed = totalTests - totalPassed;
|
|
468
|
+
if (failed > 0) {
|
|
469
|
+
console.log(` Failed: ${failed}`);
|
|
470
|
+
}
|
|
471
|
+
console.log(` Duration: ${totalDuration}ms`);
|
|
472
|
+
if (failed === 0) {
|
|
473
|
+
console.log(uiSuccess('All tests passed'));
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
console.log(uiError(`${failed} test(s) failed`));
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Watch for file changes and re-run tests
|
|
481
|
+
*/
|
|
482
|
+
async function watchAndRerun(testPath, options, context) {
|
|
483
|
+
const { logger } = context;
|
|
484
|
+
// Dynamic import for file watching
|
|
485
|
+
const chokidar = await import('chokidar');
|
|
486
|
+
logger.info('Watching for file changes...');
|
|
487
|
+
const watcher = chokidar.watch([testPath, '**/*.bin'], {
|
|
488
|
+
persistent: true,
|
|
489
|
+
ignoreInitial: true
|
|
490
|
+
});
|
|
491
|
+
watcher.on('change', async (filePath) => {
|
|
492
|
+
logger.info(`File changed: ${filePath}`);
|
|
493
|
+
logger.info('Re-running tests...');
|
|
494
|
+
try {
|
|
495
|
+
// Re-run the test command
|
|
496
|
+
const testSuites = await discoverTestSuites(testPath, options, logger);
|
|
497
|
+
const sdk = FiveSDK.create({ debug: options.verbose });
|
|
498
|
+
const results = await runTestSuites(testSuites, sdk, options, context);
|
|
499
|
+
displayTestResults(results, options, logger);
|
|
500
|
+
}
|
|
501
|
+
catch (error) {
|
|
502
|
+
logger.error(`Test re-run failed: ${error}`);
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
// Handle graceful shutdown
|
|
506
|
+
process.on('SIGINT', () => {
|
|
507
|
+
logger.info('Stopping test watcher...');
|
|
508
|
+
watcher.close();
|
|
509
|
+
process.exit(0);
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Run on-chain tests with deploy + execute pipeline
|
|
514
|
+
*/
|
|
515
|
+
async function runOnChainTests(testPath, options, context) {
|
|
516
|
+
const { logger } = context;
|
|
517
|
+
logger.info('Starting on-chain test pipeline');
|
|
518
|
+
try {
|
|
519
|
+
// Apply configuration with CLI overrides
|
|
520
|
+
const configManager = ConfigManager.getInstance();
|
|
521
|
+
const overrides = {
|
|
522
|
+
target: options.target,
|
|
523
|
+
network: options.network,
|
|
524
|
+
keypair: options.keypair
|
|
525
|
+
};
|
|
526
|
+
const config = await configManager.applyOverrides(overrides);
|
|
527
|
+
const targetPrefix = ConfigManager.getTargetPrefix(config.target);
|
|
528
|
+
logger.info(`${targetPrefix} Testing on ${config.target}`);
|
|
529
|
+
logger.info(`Network: ${config.networks[config.target].rpcUrl}`);
|
|
530
|
+
logger.info(`Keypair: ${config.keypairPath}`);
|
|
531
|
+
// Discover .bin test files
|
|
532
|
+
const testFiles = await discoverBinFiles(testPath, options);
|
|
533
|
+
if (testFiles.length === 0) {
|
|
534
|
+
logger.warn('No .bin test files found');
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
logger.info(`Found ${testFiles.length} test script(s)`);
|
|
538
|
+
// Setup Solana connection and keypair
|
|
539
|
+
const connection = new Connection(config.networks[config.target].rpcUrl, 'confirmed');
|
|
540
|
+
const signerKeypair = await loadKeypair(config.keypairPath);
|
|
541
|
+
logger.info(`Deployer: ${signerKeypair.publicKey.toString()}`);
|
|
542
|
+
// Run batch testing
|
|
543
|
+
const results = await runBatchOnChainTests(testFiles, connection, signerKeypair, options, config);
|
|
544
|
+
// Display comprehensive results
|
|
545
|
+
displayOnChainTestResults(results, options, logger);
|
|
546
|
+
// Exit with appropriate code
|
|
547
|
+
if (results.failed > 0) {
|
|
548
|
+
logger.error(`${results.failed}/${results.totalScripts} tests failed`);
|
|
549
|
+
process.exit(1);
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
logger.info(`All ${results.passed}/${results.totalScripts} tests passed`);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
catch (error) {
|
|
556
|
+
logger.error('On-chain testing failed:', error);
|
|
557
|
+
throw error;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Run tests using modern SDK-based test runner
|
|
562
|
+
*/
|
|
563
|
+
async function runWithSdkRunner(testPath, options, context) {
|
|
564
|
+
const { logger } = context;
|
|
565
|
+
logger.info('Using Five SDK test runner');
|
|
566
|
+
// Create test runner with options
|
|
567
|
+
const runner = new FiveTestRunner({
|
|
568
|
+
timeout: options.timeout,
|
|
569
|
+
maxComputeUnits: options.maxCu,
|
|
570
|
+
parallel: options.parallel || 0,
|
|
571
|
+
verbose: options.verbose,
|
|
572
|
+
debug: options.verbose,
|
|
573
|
+
trace: options.verbose,
|
|
574
|
+
pattern: options.filter || '*',
|
|
575
|
+
failFast: false
|
|
576
|
+
});
|
|
577
|
+
try {
|
|
578
|
+
// Discover test suites
|
|
579
|
+
const testSuites = await runner.discoverTestSuites(testPath);
|
|
580
|
+
if (testSuites.length === 0) {
|
|
581
|
+
logger.warn('No test files found');
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
logger.info(`Found ${testSuites.length} test suite(s)`);
|
|
585
|
+
// Run test suites
|
|
586
|
+
const results = await runner.runTestSuites(testSuites);
|
|
587
|
+
// Display results in requested format
|
|
588
|
+
if (options.format === 'json') {
|
|
589
|
+
console.log(JSON.stringify(results, null, 2));
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
displaySdkTestResults(results, logger);
|
|
593
|
+
}
|
|
594
|
+
// Check for failures
|
|
595
|
+
const totalFailed = results.reduce((sum, r) => sum + r.failed, 0);
|
|
596
|
+
if (totalFailed > 0) {
|
|
597
|
+
process.exit(1);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
catch (error) {
|
|
601
|
+
logger.error('SDK Test Runner failed:', error);
|
|
602
|
+
process.exit(1);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Display SDK test results
|
|
607
|
+
*/
|
|
608
|
+
function displaySdkTestResults(results, logger) {
|
|
609
|
+
logger.info('\nTest Results Summary:');
|
|
610
|
+
let totalPassed = 0;
|
|
611
|
+
let totalFailed = 0;
|
|
612
|
+
let totalSkipped = 0;
|
|
613
|
+
let totalDuration = 0;
|
|
614
|
+
for (const result of results) {
|
|
615
|
+
totalPassed += result.passed;
|
|
616
|
+
totalFailed += result.failed;
|
|
617
|
+
totalSkipped += result.skipped;
|
|
618
|
+
totalDuration += result.duration;
|
|
619
|
+
const status = result.failed === 0 ? 'OK' : 'FAIL';
|
|
620
|
+
logger.info(`${status} ${result.suite.name}: ${result.passed}/${result.passed + result.failed + result.skipped} passed (${result.duration}ms)`);
|
|
621
|
+
if (result.failed > 0) {
|
|
622
|
+
const failedTests = result.results.filter((r) => !r.passed);
|
|
623
|
+
for (const test of failedTests) {
|
|
624
|
+
logger.error(` FAIL ${test.name}: ${test.error || 'Test failed'}`);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
logger.info(`\nOverall: ${totalPassed} passed, ${totalFailed} failed, ${totalSkipped} skipped (${totalDuration}ms)`);
|
|
629
|
+
if (totalFailed === 0) {
|
|
630
|
+
logger.info(uiSuccess('All tests passed'));
|
|
631
|
+
}
|
|
632
|
+
else {
|
|
633
|
+
logger.error(uiError(`${totalFailed} test(s) failed`));
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Get total test count across all suites
|
|
638
|
+
*/
|
|
639
|
+
function getTotalTestCount(testSuites) {
|
|
640
|
+
return testSuites.reduce((total, suite) => total + suite.testCases.length, 0);
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Discover .bin files for on-chain testing
|
|
644
|
+
*/
|
|
645
|
+
async function discoverBinFiles(testPath, options) {
|
|
646
|
+
const binFiles = [];
|
|
647
|
+
try {
|
|
648
|
+
const stats = await stat(testPath);
|
|
649
|
+
if (stats.isFile()) {
|
|
650
|
+
// Single file - check if it's a supported artifact
|
|
651
|
+
if (testPath.endsWith('.bin') || testPath.endsWith('.five')) {
|
|
652
|
+
binFiles.push(testPath);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
else if (stats.isDirectory()) {
|
|
656
|
+
// Directory - recursively find all artifact files
|
|
657
|
+
const files = await readdir(testPath, { recursive: true });
|
|
658
|
+
for (const file of files) {
|
|
659
|
+
if (typeof file === 'string' &&
|
|
660
|
+
(file.endsWith('.bin') || file.endsWith('.five'))) {
|
|
661
|
+
const fullPath = join(testPath, file);
|
|
662
|
+
// Skip node_modules directories
|
|
663
|
+
if (fullPath.includes('node_modules')) {
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
try {
|
|
667
|
+
// Verify it's actually a file, not a directory
|
|
668
|
+
const fileStats = await stat(fullPath);
|
|
669
|
+
if (fileStats.isFile()) {
|
|
670
|
+
binFiles.push(fullPath);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
catch (error) {
|
|
674
|
+
// Skip files that can't be accessed
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
catch (error) {
|
|
682
|
+
console.warn(`Failed to discover .bin files at ${testPath}: ${error}`);
|
|
683
|
+
}
|
|
684
|
+
// Apply filter if specified
|
|
685
|
+
if (options.filter) {
|
|
686
|
+
return binFiles.filter(file => basename(file).includes(options.filter));
|
|
687
|
+
}
|
|
688
|
+
return binFiles.sort(); // Sort for consistent ordering
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Load keypair from file path
|
|
692
|
+
*/
|
|
693
|
+
async function loadKeypair(keypairPath) {
|
|
694
|
+
try {
|
|
695
|
+
const keypairData = await readFile(keypairPath, 'utf8');
|
|
696
|
+
const secretKey = JSON.parse(keypairData);
|
|
697
|
+
return Keypair.fromSecretKey(new Uint8Array(secretKey));
|
|
698
|
+
}
|
|
699
|
+
catch (error) {
|
|
700
|
+
throw new Error(`Failed to load keypair from ${keypairPath}: ${error}`);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Run batch on-chain tests with deploy → execute → verify pipeline
|
|
705
|
+
*/
|
|
706
|
+
async function runBatchOnChainTests(testFiles, connection, signerKeypair, options, config) {
|
|
707
|
+
const results = [];
|
|
708
|
+
const startTime = Date.now();
|
|
709
|
+
let totalCost = 0;
|
|
710
|
+
console.log('\n' + section(`Running ${testFiles.length} On-Chain Tests`));
|
|
711
|
+
for (let i = 0; i < testFiles.length; i++) {
|
|
712
|
+
const scriptFile = testFiles[i];
|
|
713
|
+
const scriptName = basename(scriptFile, '.bin');
|
|
714
|
+
const testStartTime = Date.now();
|
|
715
|
+
const spinner = ora(`[${i + 1}/${testFiles.length}] Testing ${scriptName}...`).start();
|
|
716
|
+
try {
|
|
717
|
+
// Load bytecode using centralized manager
|
|
718
|
+
const fileManager = FiveFileManager.getInstance();
|
|
719
|
+
const loadedFile = await fileManager.loadFile(scriptFile, {
|
|
720
|
+
validateFormat: true
|
|
721
|
+
});
|
|
722
|
+
const bytecode = loadedFile.bytecode;
|
|
723
|
+
if (options.verbose || options.debug) {
|
|
724
|
+
spinner.text = `[${i + 1}/${testFiles.length}] Deploying ${scriptName} (${bytecode.length} bytes)...`;
|
|
725
|
+
}
|
|
726
|
+
// Deploy script
|
|
727
|
+
const deployResult = await FiveSDK.deployToSolana(bytecode, connection, signerKeypair, {
|
|
728
|
+
debug: options.verbose || options.debug || false,
|
|
729
|
+
network: config.target,
|
|
730
|
+
computeBudget: 1000000,
|
|
731
|
+
maxRetries: 3
|
|
732
|
+
});
|
|
733
|
+
if (!deployResult.success) {
|
|
734
|
+
spinner.fail(`[${i + 1}/${testFiles.length}] ${scriptName} deployment failed`);
|
|
735
|
+
results.push({
|
|
736
|
+
scriptFile,
|
|
737
|
+
passed: false,
|
|
738
|
+
deployResult: {
|
|
739
|
+
success: false,
|
|
740
|
+
error: deployResult.error,
|
|
741
|
+
cost: deployResult.deploymentCost || 0
|
|
742
|
+
},
|
|
743
|
+
totalDuration: Date.now() - testStartTime,
|
|
744
|
+
totalCost: deployResult.deploymentCost || 0,
|
|
745
|
+
error: `Deployment failed: ${deployResult.error}`
|
|
746
|
+
});
|
|
747
|
+
totalCost += deployResult.deploymentCost || 0;
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
if (options.verbose || options.debug) {
|
|
751
|
+
spinner.text = `[${i + 1}/${testFiles.length}] Executing ${scriptName}...`;
|
|
752
|
+
}
|
|
753
|
+
// Execute script (function 0 with no parameters)
|
|
754
|
+
const executeResult = await FiveSDK.executeOnSolana(deployResult.programId, connection, signerKeypair, 0, // Function index 0
|
|
755
|
+
[], // No parameters
|
|
756
|
+
[], // No additional accounts
|
|
757
|
+
{
|
|
758
|
+
debug: options.verbose || options.debug || false,
|
|
759
|
+
network: config.target,
|
|
760
|
+
computeUnitLimit: 1000000,
|
|
761
|
+
maxRetries: 3
|
|
762
|
+
});
|
|
763
|
+
const testDuration = Date.now() - testStartTime;
|
|
764
|
+
const testCost = (deployResult.deploymentCost || 0) + (executeResult.cost || 0);
|
|
765
|
+
totalCost += testCost;
|
|
766
|
+
const passed = deployResult.success && executeResult.success;
|
|
767
|
+
if (passed) {
|
|
768
|
+
spinner.succeed(`[${i + 1}/${testFiles.length}] ${scriptName} OK (${testDuration}ms, ${(testCost / 1e9).toFixed(4)} SOL)`);
|
|
769
|
+
}
|
|
770
|
+
else {
|
|
771
|
+
spinner.fail(`[${i + 1}/${testFiles.length}] ${scriptName} FAIL (${testDuration}ms)`);
|
|
772
|
+
}
|
|
773
|
+
results.push({
|
|
774
|
+
scriptFile,
|
|
775
|
+
passed,
|
|
776
|
+
deployResult: {
|
|
777
|
+
success: deployResult.success,
|
|
778
|
+
scriptAccount: deployResult.programId,
|
|
779
|
+
transactionId: deployResult.transactionId,
|
|
780
|
+
cost: deployResult.deploymentCost || 0,
|
|
781
|
+
error: deployResult.error
|
|
782
|
+
},
|
|
783
|
+
executeResult: {
|
|
784
|
+
success: executeResult.success,
|
|
785
|
+
transactionId: executeResult.transactionId,
|
|
786
|
+
computeUnitsUsed: executeResult.computeUnitsUsed,
|
|
787
|
+
result: executeResult.result,
|
|
788
|
+
error: executeResult.error
|
|
789
|
+
},
|
|
790
|
+
totalDuration: testDuration,
|
|
791
|
+
totalCost: testCost
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
catch (error) {
|
|
795
|
+
const testDuration = Date.now() - testStartTime;
|
|
796
|
+
spinner.fail(`[${i + 1}/${testFiles.length}] ${scriptName} FAIL (error)`);
|
|
797
|
+
results.push({
|
|
798
|
+
scriptFile,
|
|
799
|
+
passed: false,
|
|
800
|
+
totalDuration: testDuration,
|
|
801
|
+
totalCost: 0,
|
|
802
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
const totalDuration = Date.now() - startTime;
|
|
807
|
+
const passed = results.filter(r => r.passed).length;
|
|
808
|
+
const failed = results.length - passed;
|
|
809
|
+
return {
|
|
810
|
+
totalScripts: testFiles.length,
|
|
811
|
+
passed,
|
|
812
|
+
failed,
|
|
813
|
+
totalCost,
|
|
814
|
+
totalDuration,
|
|
815
|
+
results
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Display comprehensive on-chain test results
|
|
820
|
+
*/
|
|
821
|
+
function displayOnChainTestResults(summary, options, logger) {
|
|
822
|
+
console.log('\n' + section('On-Chain Test Results'));
|
|
823
|
+
// Overall statistics
|
|
824
|
+
const successRate = ((summary.passed / summary.totalScripts) * 100).toFixed(1);
|
|
825
|
+
const avgCostPerScript = summary.totalCost / summary.totalScripts;
|
|
826
|
+
const totalCostSOL = summary.totalCost / 1e9;
|
|
827
|
+
console.log(`Passed: ${summary.passed}/${summary.totalScripts} (${successRate}%)`);
|
|
828
|
+
console.log(`Total duration: ${summary.totalDuration}ms`);
|
|
829
|
+
console.log(`Total cost: ${totalCostSOL.toFixed(6)} SOL`);
|
|
830
|
+
console.log(`Average cost per script: ${(avgCostPerScript / 1e9).toFixed(6)} SOL`);
|
|
831
|
+
if (options.analyzeCosts) {
|
|
832
|
+
console.log('\n' + section('Cost Analysis'));
|
|
833
|
+
let deploymentCost = 0;
|
|
834
|
+
let executionCost = 0;
|
|
835
|
+
for (const result of summary.results) {
|
|
836
|
+
if (result.deployResult?.cost) {
|
|
837
|
+
deploymentCost += result.deployResult.cost;
|
|
838
|
+
}
|
|
839
|
+
if (result.executeResult?.cost) {
|
|
840
|
+
executionCost += result.executeResult.cost;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
console.log(`Total deployment cost: ${(deploymentCost / 1e9).toFixed(6)} SOL`);
|
|
844
|
+
console.log(`Total execution cost: ${(executionCost / 1e9).toFixed(6)} SOL`);
|
|
845
|
+
console.log(`Deployment vs Execution: ${((deploymentCost / summary.totalCost) * 100).toFixed(1)}% : ${((executionCost / summary.totalCost) * 100).toFixed(1)}%`);
|
|
846
|
+
}
|
|
847
|
+
// Failed tests details
|
|
848
|
+
if (summary.failed > 0) {
|
|
849
|
+
console.log('\n' + section('Failed Tests'));
|
|
850
|
+
const failedResults = summary.results.filter(r => !r.passed);
|
|
851
|
+
for (const result of failedResults) {
|
|
852
|
+
const scriptName = basename(result.scriptFile, '.bin');
|
|
853
|
+
console.log(` FAIL ${scriptName}:`);
|
|
854
|
+
if (result.error) {
|
|
855
|
+
console.log(` Error: ${result.error}`);
|
|
856
|
+
}
|
|
857
|
+
if (result.deployResult && !result.deployResult.success) {
|
|
858
|
+
console.log(` Deployment: Failed - ${result.deployResult.error}`);
|
|
859
|
+
}
|
|
860
|
+
if (result.executeResult && !result.executeResult.success) {
|
|
861
|
+
console.log(` Execution: Failed - ${result.executeResult.error}`);
|
|
862
|
+
}
|
|
863
|
+
console.log(` Duration: ${result.totalDuration}ms`);
|
|
864
|
+
console.log(` Cost: ${(result.totalCost / 1e9).toFixed(6)} SOL\n`);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
// Successful tests (if verbose)
|
|
868
|
+
if (options.verbose && summary.passed > 0) {
|
|
869
|
+
console.log('\n' + section('Successful Tests'));
|
|
870
|
+
const passedResults = summary.results.filter(r => r.passed);
|
|
871
|
+
for (const result of passedResults) {
|
|
872
|
+
const scriptName = basename(result.scriptFile, '.bin');
|
|
873
|
+
const deployTx = result.deployResult?.transactionId?.substring(0, 8) || 'N/A';
|
|
874
|
+
const executeTx = result.executeResult?.transactionId?.substring(0, 8) || 'N/A';
|
|
875
|
+
const computeUnits = result.executeResult?.computeUnitsUsed || 0;
|
|
876
|
+
console.log(` OK ${scriptName}:`);
|
|
877
|
+
console.log(` Deploy: ${deployTx}... | Execute: ${executeTx}...`);
|
|
878
|
+
console.log(` Compute Units: ${computeUnits.toLocaleString()}`);
|
|
879
|
+
console.log(` Duration: ${result.totalDuration}ms | Cost: ${(result.totalCost / 1e9).toFixed(6)} SOL\n`);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
// Summary message
|
|
883
|
+
if (summary.failed === 0) {
|
|
884
|
+
console.log(uiSuccess('All on-chain tests passed'));
|
|
885
|
+
}
|
|
886
|
+
else {
|
|
887
|
+
console.log(uiError(`${summary.failed} test(s) failed. Check logs above for details.`));
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
//# sourceMappingURL=test.js.map
|