-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Auto tests #45
Auto tests #45
Changes from 5 commits
f14d7fe
f0d1c00
6e17b03
e58acea
3ea2c4f
ffc256d
76ea18c
79b76cf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
module.exports = { | ||
env: { | ||
browser: true, | ||
es2021: true, | ||
}, | ||
extends: "standard-with-typescript", | ||
overrides: [ | ||
{ | ||
env: { | ||
node: true, | ||
}, | ||
files: [".eslintrc.{js,cjs}"], | ||
parserOptions: { | ||
sourceType: "script", | ||
}, | ||
}, | ||
], | ||
parserOptions: { | ||
ecmaVersion: "latest", | ||
sourceType: "module", | ||
project: true | ||
}, | ||
ignorePatterns: ["react-app-env.d.ts", "reportWebVitals.ts"], | ||
rules: { | ||
"@typescript-eslint/explicit-function-return-type": 0, | ||
"@typescript-eslint/space-before-function-paren": 0, | ||
"@typescript-eslint/comma-dangle": 0, | ||
"@typescript-eslint/prefer-nullish-coalescing" : 0, | ||
"@typescript-eslint/strict-boolean-expressions" : 0, | ||
"@typescript-eslint/restrict-plus-operands" : 0, | ||
"@typescript-eslint/no-base-to-string" : 0, | ||
"@typescript-eslint/restrict-template-expressions" : 0, | ||
"@typescript-eslint/indent" : 0 | ||
}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
|
||
/.vscode | ||
|
||
# dependencies | ||
/node_modules | ||
/dist | ||
/.pnp | ||
.pnp.js | ||
|
||
# testing | ||
/coverage | ||
|
||
# production | ||
/build | ||
|
||
# misc | ||
.DS_Store | ||
.env.local | ||
.env.development.local | ||
.env.test.local | ||
.env.production.local | ||
|
||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"semi": false, | ||
"singleQuote": true, | ||
"arrowParens": "avoid", | ||
"trailingComma": "none", | ||
"singleAttributePerLine": false | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
// Use IntelliSense to learn about possible attributes. | ||
// Hover to view descriptions of existing attributes. | ||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 | ||
"version": "0.2.0", | ||
"configurations": [ | ||
{ | ||
"type": "node", | ||
"request": "launch", | ||
"name": "Launch Program", | ||
"skipFiles": [ | ||
"<node_internals>/**" | ||
], | ||
"program": "${workspaceFolder}/app.ts", | ||
"preLaunchTask": "tsc: build - tsconfig.json", | ||
"outFiles": [ | ||
"${workspaceFolder}/dist/**/*.js" | ||
] | ||
} | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { exit } from 'process' | ||
import { TestsRunner } from './testsrunner' | ||
|
||
async function start() { | ||
const testsRunner = new TestsRunner() | ||
const res = await testsRunner.runTests() | ||
if (!res) { | ||
exit(-1) | ||
} | ||
} | ||
|
||
void start() |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,15 @@ | ||||||
import configJson from './config.json' | ||||||
|
||||||
export class AppConfig { | ||||||
public friendbot: string = '' | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Is this possible? We would not need the |
||||||
public server: string = '' | ||||||
public network: string = '' | ||||||
public showLogs: boolean = true | ||||||
|
||||||
public parseConfig() { | ||||||
this.friendbot = configJson.server.friendbot | ||||||
this.server = configJson.server.url | ||||||
this.network = configJson.server.network | ||||||
this.showLogs = configJson.show_logs | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"server" : { | ||
"friendbot": "https://friendbot-futurenet.stellar.org/", | ||
"url": "https://horizon-futurenet.stellar.org/", | ||
"network": "futurenet" | ||
}, | ||
"show_logs": false | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
import StellarSdk from 'stellar-sdk' | ||
import SorobanClient from 'soroban-client' | ||
import { spawn } from 'child_process' | ||
import type { AppConfig } from '../appconfig' | ||
|
||
export enum OperationType { | ||
UploadContract, | ||
CreateContract, | ||
InvokeMethod, | ||
} | ||
|
||
export class OperationFee { | ||
fee: number | ||
operationType: OperationType | ||
methodName: string | ||
} | ||
|
||
export abstract class BaseTests { | ||
fees: Map<string, OperationFee[]> = new Map<string, OperationFee[]>() | ||
deployFee: number = 0 | ||
invokeFee: number = 0 | ||
protected logs: string[] = new Array<string>() | ||
protected accounts: Map<string, string[]> = new Map<string, string[]>([ | ||
['admin', []], | ||
['player_1', []], | ||
['player_2', []], | ||
['player_3', []], | ||
]) | ||
|
||
protected contractId: string = '' | ||
|
||
constructor(protected config: AppConfig) {} | ||
|
||
async createAccounts() { | ||
let resultOk = true | ||
for (const acc of this.accounts.keys()) { | ||
await this.runSoroban(['config', 'identity', 'generate', acc]) | ||
await this.runSoroban(['config', 'identity', 'address', acc]).then( | ||
async data => { | ||
const id = data.toString().trim() | ||
await this.runSoroban(['config', 'identity', 'show', acc]).then( | ||
pwd => { | ||
this.accounts.set(acc, [id, pwd.toString().trim()]) | ||
} | ||
) | ||
const url = `${this.config.friendbot}?addr=${id}` | ||
const res = await fetch(url) | ||
const json = await res.json() | ||
|
||
if (this.config.showLogs) { | ||
console.log(json) | ||
} | ||
}, | ||
error => { | ||
resultOk = false | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This continues executing even if the result is false. Why not use Promises.all There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, we may want to introspect when something fails, so we might prefer to use |
||
const strError: string = error.toString() | ||
console.error(`error: ${strError}`) | ||
} | ||
) | ||
} | ||
return resultOk | ||
} | ||
|
||
async deployContract(contract: string) { | ||
let result = true | ||
await this.runSoroban([ | ||
'contract', | ||
'deploy', | ||
geofmureithi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
'--source', | ||
'admin', | ||
'--wasm', | ||
`../target/wasm32-unknown-unknown/release/${contract}.wasm`, | ||
'--network', | ||
this.config.network, | ||
]).then( | ||
data => { | ||
this.contractId = data.toString().trim() | ||
}, | ||
error => { | ||
result = false | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just return the promise and handle the result upstream |
||
console.error(error) | ||
} | ||
) | ||
return result | ||
} | ||
|
||
protected async runSoroban(args: string[]) { | ||
const proc = spawn('soroban', args) | ||
let data: string = '' | ||
let error: string = '' | ||
for await (const chunk of proc.stdout) { | ||
data += chunk | ||
} | ||
for await (const chunk of proc.stderr) { | ||
error += chunk | ||
} | ||
const exitCode = await new Promise((resolve, reject) => { | ||
proc.on('close', resolve) | ||
}) | ||
|
||
if (exitCode || error.length > 0) { | ||
throw new Error(`soroban error exit ${exitCode}, ${error}`) | ||
} | ||
return data | ||
} | ||
|
||
protected findInvokeMethodsName(envelope) { | ||
for (const value of envelope._value._attributes.tx._attributes.operations[0] | ||
._attributes.body._value._attributes.hostFunction._value) { | ||
const scv = value._switch.name | ||
if (scv === 'scvSymbol') { | ||
return value._value.toString() | ||
} | ||
} | ||
return '' | ||
} | ||
|
||
protected async calculateFeesForAccount(account: string) { | ||
const server = new StellarSdk.Server(this.config.server) | ||
await server | ||
.transactions() | ||
.forAccount(this.accounts.get(account)[0]) | ||
.call() | ||
.then(r => { | ||
const adminFees = new Array<OperationFee>() | ||
for (let i = 1; i < r.records.length; ++i) { | ||
const tx = r.records[i] | ||
const envelope = SorobanClient.xdr.TransactionEnvelope.fromXDR( | ||
tx.envelope_xdr, | ||
'base64' | ||
) | ||
const type = | ||
envelope._value._attributes.tx._attributes.operations[0]._attributes | ||
.body._value._attributes.hostFunction._switch.name | ||
const operationFee = new OperationFee() | ||
operationFee.fee = Number.parseInt(tx.fee_charged) | ||
if (type === 'hostFunctionTypeUploadContractWasm') { | ||
operationFee.operationType = OperationType.UploadContract | ||
operationFee.methodName = 'UploadContract' | ||
this.deployFee += operationFee.fee | ||
} | ||
if (type === 'hostFunctionTypeCreateContract') { | ||
operationFee.operationType = OperationType.CreateContract | ||
operationFee.methodName = 'CreateContract' | ||
this.deployFee += operationFee.fee | ||
} | ||
if (type === 'hostFunctionTypeInvokeContract') { | ||
operationFee.operationType = OperationType.InvokeMethod | ||
operationFee.methodName = this.findInvokeMethodsName(envelope) | ||
this.invokeFee += operationFee.fee | ||
} | ||
adminFees.push(operationFee) | ||
if (this.config.showLogs) { | ||
console.log( | ||
`${operationFee.methodName} fee: ${operationFee.fee.toString()}` | ||
) | ||
} | ||
} | ||
this.fees.set(account, adminFees) | ||
}) | ||
} | ||
|
||
abstract run(): Promise<boolean> | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this file needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I'll remove it.