Skip to content
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

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions tests/.eslintrc.cjs
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
},
};
26 changes: 26 additions & 0 deletions tests/.gitignore
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*
7 changes: 7 additions & 0 deletions tests/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"semi": false,
"singleQuote": true,
"arrowParens": "avoid",
"trailingComma": "none",
"singleAttributePerLine": false
}
21 changes: 21 additions & 0 deletions tests/.vscode/launch.json
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this file needed?

Copy link
Member Author

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.

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"
]
}
]
}
12 changes: 12 additions & 0 deletions tests/app.ts
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()
15 changes: 15 additions & 0 deletions tests/appconfig.ts
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 = ''
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public friendbot: string = ''
public friendbot: string = configJson.server.friendbot;

Is this possible? We would not need the parseConfig().

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
}
}
8 changes: 8 additions & 0 deletions tests/config.json
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
}
164 changes: 164 additions & 0 deletions tests/contracts/basetests.ts
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
Copy link
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Contributor

Choose a reason for hiding this comment

The 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 Promises.allSettled

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
Copy link
Contributor

Choose a reason for hiding this comment

The 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>
}
Loading