save compiler artifacts to show data on debugging

pull/1573/head
aniket-engg 3 years ago committed by Aniket
parent 6edbb46be8
commit 52b2e4686d
  1. 15
      apps/remix-ide/src/app/tabs/test-tab.js
  2. 5
      libs/remix-core-plugin/src/lib/compiler-artefacts.ts
  3. 1
      libs/remix-tests/src/compiler.ts
  4. 2
      libs/remix-tests/src/index.ts
  5. 68
      libs/remix-tests/src/lib/eventManager.ts
  6. 227
      libs/remix-tests/src/runTestSources.ts

@ -7,7 +7,7 @@ var async = require('async')
var tooltip = require('../ui/tooltip')
var Renderer = require('../ui/renderer')
var css = require('./styles/test-tab-styles')
var remixTests = require('@remix-project/remix-tests')
var { UnitTestRunner } = require('@remix-project/remix-tests')
const TestTabLogic = require('./testTab/testTab')
@ -33,6 +33,7 @@ module.exports = class TestTab extends ViewPlugin {
this.data = {}
this.appManager = appManager
this.renderer = new Renderer(this)
this.testRunner = new UnitTestRunner()
this.hasBeenStopped = false
this.runningTestsNumber = 0
this.readyTestsNumber = 0
@ -95,6 +96,14 @@ module.exports = class TestTab extends ViewPlugin {
this.setCurrentPath(this.defaultPath)
})
this.testRunner.event.register('compilationFinished', (success, data, source) => {
if (success) {
// forwarding the event to the appManager infra
// This is listened by compilerArtefacts to show data while debugging
this.emit('compilationFinished', source.target, source, 'soljson', data)
}
})
this.fileManager.events.on('noFileSelected', () => {
})
@ -462,7 +471,7 @@ module.exports = class TestTab extends ViewPlugin {
usingWorker: canUseWorker(currentVersion),
runs
}
remixTests.runTestSources(runningTest, compilerConfig, () => {}, () => {}, (error, result) => {
this.testRunner.runTestSources(runningTest, compilerConfig, () => {}, () => {}, (error, result) => {
if (error) return reject(error)
resolve(result)
}, (url, cb) => {
@ -489,7 +498,7 @@ module.exports = class TestTab extends ViewPlugin {
usingWorker: canUseWorker(currentVersion),
runs
}
remixTests.runTestSources(
this.testRunner.runTestSources(
runningTests,
compilerConfig,
(result) => this.testCallback(result, runningTests),

@ -52,6 +52,11 @@ export class CompilerArtefacts extends Plugin {
this.compilersArtefacts.__last = new CompilerAbstract(languageVersion, data, source)
saveCompilationPerFileResult(file, source, languageVersion, data)
})
this.on('solidityUnitTesting', 'compilationFinished', (file, source, languageVersion, data) => {
this.compilersArtefacts.__last = new CompilerAbstract(languageVersion, data, source)
saveCompilationPerFileResult(file, source, languageVersion, data)
})
}
getAllContractDatas () {

@ -205,6 +205,7 @@ export function compileContractSources (sources: SrcIfc, compilerConfig: Compile
function doCompilation (next) {
// @ts-ignore
compiler.event.register('compilationFinished', this, (success, data, source) => {
if (opts && opts.event) opts.event.trigger('compilationFinished', [success, data, source])
next(null, data)
})
compiler.compile(sources, filepath)

@ -1,5 +1,5 @@
export { runTestFiles } from './runTestFiles'
export { runTestSources } from './runTestSources'
export { UnitTestRunner } from './runTestSources'
export { runTest } from './testRunner'
export * from './types'
export const assertLibCode = require('../sol/tests.sol')

@ -0,0 +1,68 @@
'use strict'
export default class EventManager {
registered: any = {} // eslint-disable-line
anonymous: any = {} // eslint-disable-line
/*
* Unregister a listener.
* Note that if obj is a function. the unregistration will be applied to the dummy obj {}.
*
* @param {String} eventName - the event name
* @param {Object or Func} obj - object that will listen on this event
* @param {Func} func - function of the listeners that will be executed
*/
unregister (eventName: any, obj: any, func: any): void { // eslint-disable-line
if (!this.registered[eventName]) {
return
}
if (obj instanceof Function) {
func = obj
obj = this.anonymous
}
for (const reg in this.registered[eventName]) {
if (this.registered[eventName][reg].obj === obj && this.registered[eventName][reg].func === func) {
this.registered[eventName].splice(reg, 1)
}
}
}
/*
* Register a new listener.
* Note that if obj is a function, the function registration will be associated with the dummy object {}
*
* @param {String} eventName - the event name
* @param {Object or Func} obj - object that will listen on this event
* @param {Func} func - function of the listeners that will be executed
*/
register (eventName: any, obj: any, func: any): void { // eslint-disable-line
if (!this.registered[eventName]) {
this.registered[eventName] = []
}
if (obj instanceof Function) {
func = obj
obj = this.anonymous
}
this.registered[eventName].push({
obj: obj,
func: func
})
}
/*
* trigger event.
* Every listener have their associated function executed
*
* @param {String} eventName - the event name
* @param {Array}j - argument that will be passed to the executed function.
*/
trigger (eventName: any, args: any): void { // eslint-disable-line
if (!this.registered[eventName]) {
return
}
for (const listener in this.registered[eventName]) {
const l = this.registered[eventName][listener]
if (l.func) l.func.apply(l.obj === this.anonymous ? {} : l.obj, args)
}
}
}

@ -5,6 +5,7 @@ import { deployAll } from './deployer'
import { runTest } from './testRunner'
import Web3 from 'web3'
import EventManager from './lib/eventManager'
import { Provider, extend } from '@remix-project/remix-simulator'
import {
FinalResult, SrcIfc, compilationInterface, ASTInterface, Options,
@ -12,126 +13,134 @@ import {
} from './types'
require('colors')
const createWeb3Provider = async function () {
const web3 = new Web3()
const provider: any = new Provider()
await provider.init()
web3.setProvider(provider)
extend(web3)
return web3
}
export class UnitTestRunner {
event
/**
* @dev Run tests from source of a test contract file (used for IDE)
* @param contractSources Sources of contract
* @param compilerConfig current compiler configuration
* @param testCallback Test callback
* @param resultCallback Result Callback
* @param finalCallback Final Callback
* @param importFileCb Import file callback
* @param opts Options
*/
export async function runTestSources (contractSources: SrcIfc, compilerConfig: CompilerConfiguration, testCallback, resultCallback, finalCallback: any, importFileCb, opts: Options) {
opts = opts || {}
const sourceASTs: any = {}
const web3 = opts.web3 || await createWeb3Provider()
let accounts: string[] | null = opts.accounts || null
async.waterfall([
function getAccountList (next) {
if (accounts) return next()
web3.eth.getAccounts((_err, _accounts) => {
accounts = _accounts
next()
})
},
function compile (next) {
compileContractSources(contractSources, compilerConfig, importFileCb, { accounts }, next)
},
function deployAllContracts (compilationResult: compilationInterface, asts: ASTInterface, next) {
for (const filename in asts) {
if (filename.endsWith('_test.sol')) { sourceASTs[filename] = asts[filename].ast }
}
deployAll(compilationResult, web3, false, (err, contracts) => {
if (err) {
// If contract deployment fails because of 'Out of Gas' error, try again with double gas
// This is temporary, should be removed when remix-tests will have a dedicated UI to
// accept deployment params from UI
if (err.message.includes('The contract code couldn\'t be stored, please check your gas limit')) {
deployAll(compilationResult, web3, true, (error, contracts) => {
if (error) next([{ message: 'contract deployment failed after trying twice: ' + error.message, severity: 'error' }]) // IDE expects errors in array
else next(null, compilationResult, contracts)
})
} else { next([{ message: 'contract deployment failed: ' + err.message, severity: 'error' }]) } // IDE expects errors in array
} else { next(null, compilationResult, contracts) }
})
},
function determineTestContractsToRun (compilationResult: compilationInterface, contracts: any, next) {
const contractsToTest: string[] = []
const contractsToTestDetails: any[] = []
constructor() {
this.event = new EventManager()
}
async createWeb3Provider () {
const web3 = new Web3()
const provider: any = new Provider()
await provider.init()
web3.setProvider(provider)
extend(web3)
return web3
}
for (const filename in compilationResult) {
if (!filename.endsWith('_test.sol')) {
continue
/**
* @dev Run tests from source of a test contract file (used for IDE)
* @param contractSources Sources of contract
* @param compilerConfig current compiler configuration
* @param testCallback Test callback
* @param resultCallback Result Callback
* @param finalCallback Final Callback
* @param importFileCb Import file callback
* @param opts Options
*/
async runTestSources (contractSources: SrcIfc, compilerConfig: CompilerConfiguration, testCallback, resultCallback, finalCallback: any, importFileCb, opts: Options) {
opts = opts || {}
const sourceASTs: any = {}
const web3 = opts.web3 || await this.createWeb3Provider()
let accounts: string[] | null = opts.accounts || null
async.waterfall([
function getAccountList (next) {
if (accounts) return next()
web3.eth.getAccounts((_err, _accounts) => {
accounts = _accounts
next()
})
},
(next) => {
compileContractSources(contractSources, compilerConfig, importFileCb, { accounts, event: this.event}, next)
},
function deployAllContracts (compilationResult: compilationInterface, asts: ASTInterface, next) {
for (const filename in asts) {
if (filename.endsWith('_test.sol')) { sourceASTs[filename] = asts[filename].ast }
}
Object.keys(compilationResult[filename]).forEach(contractName => {
contractsToTestDetails.push(compilationResult[filename][contractName])
contractsToTest.push(contractName)
deployAll(compilationResult, web3, false, (err, contracts) => {
if (err) {
// If contract deployment fails because of 'Out of Gas' error, try again with double gas
// This is temporary, should be removed when remix-tests will have a dedicated UI to
// accept deployment params from UI
if (err.message.includes('The contract code couldn\'t be stored, please check your gas limit')) {
deployAll(compilationResult, web3, true, (error, contracts) => {
if (error) next([{ message: 'contract deployment failed after trying twice: ' + error.message, severity: 'error' }]) // IDE expects errors in array
else next(null, compilationResult, contracts)
})
} else { next([{ message: 'contract deployment failed: ' + err.message, severity: 'error' }]) } // IDE expects errors in array
} else { next(null, compilationResult, contracts) }
})
}
next(null, contractsToTest, contractsToTestDetails, contracts)
},
function runTests (contractsToTest: string[], contractsToTestDetails: any[], contracts: any, next) {
let totalPassing = 0
let totalFailing = 0
let totalTime = 0
const errors: any[] = []
// eslint-disable-next-line handle-callback-err
const _testCallback = function (err: Error | null | undefined, result: TestResultInterface) {
if (result.type === 'testFailure') {
errors.push(result)
},
function determineTestContractsToRun (compilationResult: compilationInterface, contracts: any, next) {
const contractsToTest: string[] = []
const contractsToTestDetails: any[] = []
for (const filename in compilationResult) {
if (!filename.endsWith('_test.sol')) {
continue
}
Object.keys(compilationResult[filename]).forEach(contractName => {
contractsToTestDetails.push(compilationResult[filename][contractName])
contractsToTest.push(contractName)
})
}
next(null, contractsToTest, contractsToTestDetails, contracts)
},
function runTests (contractsToTest: string[], contractsToTestDetails: any[], contracts: any, next) {
let totalPassing = 0
let totalFailing = 0
let totalTime = 0
const errors: any[] = []
// eslint-disable-next-line handle-callback-err
const _testCallback = function (err: Error | null | undefined, result: TestResultInterface) {
if (result.type === 'testFailure') {
errors.push(result)
}
testCallback(result)
}
testCallback(result)
}
const _resultsCallback = function (_err, result, cb) {
resultCallback(_err, result, () => {}) // eslint-disable-line @typescript-eslint/no-empty-function
totalPassing += result.passingNum
totalFailing += result.failureNum
totalTime += result.timePassed
cb()
}
const _resultsCallback = function (_err, result, cb) {
resultCallback(_err, result, () => {}) // eslint-disable-line @typescript-eslint/no-empty-function
totalPassing += result.passingNum
totalFailing += result.failureNum
totalTime += result.timePassed
cb()
}
async.eachOfLimit(contractsToTest, 1, (contractName: string, index: string | number, cb: ErrorCallback) => {
const fileAST: AstNode = sourceASTs[contracts[contractName]['filename']]
runTest(contractName, contracts[contractName], contractsToTestDetails[index], fileAST, { accounts, web3 }, _testCallback, (err, result) => {
async.eachOfLimit(contractsToTest, 1, (contractName: string, index: string | number, cb: ErrorCallback) => {
const fileAST: AstNode = sourceASTs[contracts[contractName]['filename']]
runTest(contractName, contracts[contractName], contractsToTestDetails[index], fileAST, { accounts, web3 }, _testCallback, (err, result) => {
if (err) {
return cb(err)
}
_resultsCallback(null, result, cb)
})
}, function (err) {
if (err) {
return cb(err)
return next(err)
}
_resultsCallback(null, result, cb)
})
}, function (err) {
if (err) {
return next(err)
}
const finalResults: FinalResult = {
totalPassing: 0,
totalFailing: 0,
totalTime: 0,
errors: []
}
const finalResults: FinalResult = {
totalPassing: 0,
totalFailing: 0,
totalTime: 0,
errors: []
}
finalResults.totalPassing = totalPassing || 0
finalResults.totalFailing = totalFailing || 0
finalResults.totalTime = totalTime || 0
finalResults.errors = []
finalResults.totalPassing = totalPassing || 0
finalResults.totalFailing = totalFailing || 0
finalResults.totalTime = totalTime || 0
finalResults.errors = []
errors.forEach((error, _index) => {
finalResults.errors.push({ context: error.context, value: error.value, message: error.errMsg })
})
errors.forEach((error, _index) => {
finalResults.errors.push({ context: error.context, value: error.value, message: error.errMsg })
})
next(null, finalResults)
})
}
], finalCallback)
next(null, finalResults)
})
}
], finalCallback)
}
}

Loading…
Cancel
Save