Add tests and fixtures for full project scenarios (#385)

* Full project tests fixtures for repeated use of evm_revert, 
* ... and the skipped folders option, 
* ... and solidity imports with relative paths reaching outside the `contracts/` folder.
pull/386/head
cgewecke 5 years ago committed by GitHub
parent 826d9d59e8
commit 9ea10859b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 20
      dist/truffle.plugin.js
  2. 1
      test/integration/projects/import-paths/.gitignore
  3. 3
      test/integration/projects/import-paths/.solcover.js
  4. 10
      test/integration/projects/import-paths/assets/RelativePathImport.sol
  5. 23
      test/integration/projects/import-paths/contracts/Migrations.sol
  6. 17
      test/integration/projects/import-paths/contracts/UsesImports.sol
  7. 5
      test/integration/projects/import-paths/migrations/1_initial_migration.js
  8. 10
      test/integration/projects/import-paths/node_modules/package/NodeModulesImport.sol
  9. 16
      test/integration/projects/import-paths/test/uses_imports.js
  10. 99
      test/integration/projects/import-paths/truffle-config.js
  11. 3
      test/integration/projects/multiple-migrations/.solcover.js
  12. 17
      test/integration/projects/multiple-migrations/contracts/ContractA.sol
  13. 17
      test/integration/projects/multiple-migrations/contracts/ContractB.sol
  14. 17
      test/integration/projects/multiple-migrations/contracts/ContractC.sol
  15. 23
      test/integration/projects/multiple-migrations/contracts/Migrations.sol
  16. 5
      test/integration/projects/multiple-migrations/migrations/1_initial_migration.js
  17. 5
      test/integration/projects/multiple-migrations/migrations/2_contracta.js
  18. 5
      test/integration/projects/multiple-migrations/migrations/3_contractb.js
  19. 5
      test/integration/projects/multiple-migrations/migrations/4_contractc.js
  20. 15
      test/integration/projects/multiple-migrations/test/contracta.js
  21. 15
      test/integration/projects/multiple-migrations/test/contractb.js
  22. 20
      test/integration/projects/multiple-migrations/test/contractc.js
  23. 99
      test/integration/projects/multiple-migrations/truffle-config.js
  24. 4
      test/integration/projects/skipping/.solcover.js
  25. 17
      test/integration/projects/skipping/contracts/ContractA.sol
  26. 23
      test/integration/projects/skipping/contracts/Migrations.sol
  27. 17
      test/integration/projects/skipping/contracts/skipped-folder/ContractB.sol
  28. 5
      test/integration/projects/skipping/migrations/1_initial_migration.js
  29. 29
      test/integration/projects/skipping/test/contract.js
  30. 99
      test/integration/projects/skipping/truffle-config.js
  31. 27
      test/units/app.js
  32. 20
      test/util/integration.truffle.js

@ -37,20 +37,29 @@ const ganache = require('ganache-core-sc');
async function plugin(truffleConfig){ async function plugin(truffleConfig){
let app; let app;
let error; let error;
let truffle;
let testsErrored = false; let testsErrored = false;
let coverageConfig;
let coverageConfigPath;
// Load truffle lib, .solcover.js & launch app
try { try {
// Load truffle lib & coverage config truffle = loadTruffleLibrary();
const truffle = loadTruffleLibrary();
const coverageConfigPath = path.join(truffleConfig.working_directory, '.solcover.js'); coverageConfigPath = path.join(truffleConfig.working_directory, '.solcover.js');
const coverageConfig = req.silent(coverageConfigPath) || {}; coverageConfig = req.silent(coverageConfigPath) || {};
coverageConfig.cwd = truffleConfig.working_directory; coverageConfig.cwd = truffleConfig.working_directory;
coverageConfig.contractsDir = truffleConfig.contracts_directory; coverageConfig.contractsDir = truffleConfig.contracts_directory;
// Start
app = new App(coverageConfig); app = new App(coverageConfig);
} catch (err) {
throw err;
}
// Instrument and test..
try {
death(app.cleanUp); death(app.cleanUp);
// Write instrumented sources to temp folder // Write instrumented sources to temp folder
@ -97,7 +106,6 @@ async function plugin(truffleConfig){
} catch (e) { } catch (e) {
error = e.stack; error = e.stack;
} }
// Run Istanbul // Run Istanbul
await app.report(); await app.report();

@ -0,0 +1,3 @@
module.exports = {
silent: process.env.SILENT ? true : false,
};

@ -0,0 +1,10 @@
pragma solidity >=0.4.21 <0.6.0;
contract RelativePathImport {
uint r;
constructor() public {}
function isRelativePathMethod() public {
r = 5;
}
}

@ -0,0 +1,23 @@
pragma solidity >=0.4.21 <0.6.0;
contract Migrations {
address public owner;
uint public last_completed_migration;
constructor() public {
owner = msg.sender;
}
modifier restricted() {
if (msg.sender == owner) _;
}
function setCompleted(uint completed) public restricted {
last_completed_migration = completed;
}
function upgrade(address new_address) public restricted {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}

@ -0,0 +1,17 @@
pragma solidity >=0.4.21 <0.6.0;
import "../assets/RelativePathImport.sol";
import "package/NodeModulesImport.sol";
contract UsesImports is RelativePathImport, NodeModulesImport {
constructor() public {}
function wrapsRelativePathMethod() public {
isRelativePathMethod();
}
function wrapsNodeModulesMethod() public {
isNodeModulesMethod();
}
}

@ -0,0 +1,5 @@
const Migrations = artifacts.require("Migrations");
module.exports = function(deployer) {
deployer.deploy(Migrations);
};

@ -0,0 +1,10 @@
pragma solidity >=0.4.21 <0.6.0;
contract NodeModulesImport {
uint x;
constructor() public {}
function isNodeModulesMethod() public {
x = 5;
}
}

@ -0,0 +1,16 @@
var UsesImports = artifacts.require("UsesImports");
contract("UsesImports", function(accounts) {
let instance;
before(async () => instance = await UsesImports.new());
it('uses a method from a relative import', async () => {
await instance.wrapsRelativePathMethod();
})
it('uses an import from node_modules', async () => {
await instance.wrapsNodeModulesMethod();
})
});

@ -0,0 +1,99 @@
/**
* Use this file to configure your truffle project. It's seeded with some
* common settings for different networks and features like migrations,
* compilation and testing. Uncomment the ones you need or modify
* them to suit your project as necessary.
*
* More information about configuration can be found at:
*
* truffleframework.com/docs/advanced/configuration
*
* To deploy via Infura you'll need a wallet provider (like truffle-hdwallet-provider)
* to sign your transactions before they're sent to a remote public node. Infura accounts
* are available for free at: infura.io/register.
*
* You'll also need a mnemonic - the twelve word phrase the wallet uses to generate
* public/private key pairs. If you're publishing your code to GitHub make sure you load this
* phrase from a file you've .gitignored so it doesn't accidentally become public.
*
*/
// const HDWalletProvider = require('truffle-hdwallet-provider');
// const infuraKey = "fj4jll3k.....";
//
// const fs = require('fs');
// const mnemonic = fs.readFileSync(".secret").toString().trim();
module.exports = {
/**
* Networks define how you connect to your ethereum client and let you set the
* defaults web3 uses to send transactions. If you don't specify one truffle
* will spin up a development blockchain for you on port 9545 when you
* run `develop` or `test`. You can ask a truffle command to use a specific
* network from the command line, e.g
*
* $ truffle test --network <network-name>
*/
networks: {
// Useful for testing. The `development` name is special - truffle uses it by default
// if it's defined here and no other network is specified at the command line.
// You should run a client (like ganache-cli, geth or parity) in a separate terminal
// tab if you use this network and you must also set the `host`, `port` and `network_id`
// options below to some value.
//
// development: {
// host: "127.0.0.1", // Localhost (default: none)
// port: 8545, // Standard Ethereum port (default: none)
// network_id: "*", // Any network (default: none)
// },
// Another network with more advanced options...
// advanced: {
// port: 8777, // Custom port
// network_id: 1342, // Custom network
// gas: 8500000, // Gas sent with each transaction (default: ~6700000)
// gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei)
// from: <address>, // Account to send txs from (default: accounts[0])
// websockets: true // Enable EventEmitter interface for web3 (default: false)
// },
// Useful for deploying to a public network.
// NB: It's important to wrap the provider as a function.
// ropsten: {
// provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`),
// network_id: 3, // Ropsten's id
// gas: 5500000, // Ropsten has a lower block limit than mainnet
// confirmations: 2, // # of confs to wait between deployments. (default: 0)
// timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50)
// skipDryRun: true // Skip dry run before migrations? (default: false for public nets )
// },
// Useful for private networks
// private: {
// provider: () => new HDWalletProvider(mnemonic, `https://network.io`),
// network_id: 2111, // This network is yours, in the cloud.
// production: true // Treats this network as if it was a public net. (default: false)
// }
},
// Set default mocha options here, use special reporters etc.
mocha: {
// timeout: 100000
},
// Configure your compilers
compilers: {
solc: {
// version: "0.5.1", // Fetch exact version from solc-bin (default: truffle's version)
// docker: true, // Use "0.5.1" you've installed locally with docker (default: false)
// settings: { // See the solidity docs for advice about optimization and evmVersion
// optimizer: {
// enabled: false,
// runs: 200
// },
// evmVersion: "byzantium"
// }
}
}
}

@ -0,0 +1,3 @@
module.exports = {
silent: process.env.SILENT ? true : false,
};

@ -0,0 +1,17 @@
pragma solidity ^0.5.0;
contract ContractA {
uint x;
constructor() public {
}
function sendFn() public {
x = 5;
}
function callFn() public pure returns (uint){
uint y = 5;
return y;
}
}

@ -0,0 +1,17 @@
pragma solidity ^0.5.0;
contract ContractB {
uint x;
constructor() public {
}
function sendFn() public {
x = 5;
}
function callFn() public pure returns (uint){
uint y = 5;
return y;
}
}

@ -0,0 +1,17 @@
pragma solidity ^0.5.0;
contract ContractC {
uint x;
constructor() public {
}
function sendFn() public {
x = 5;
}
function callFn() public pure returns (uint){
uint y = 5;
return y;
}
}

@ -0,0 +1,23 @@
pragma solidity >=0.4.21 <0.6.0;
contract Migrations {
address public owner;
uint public last_completed_migration;
constructor() public {
owner = msg.sender;
}
modifier restricted() {
if (msg.sender == owner) _;
}
function setCompleted(uint completed) public restricted {
last_completed_migration = completed;
}
function upgrade(address new_address) public restricted {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}

@ -0,0 +1,5 @@
const Migrations = artifacts.require("Migrations");
module.exports = function(deployer) {
deployer.deploy(Migrations);
};

@ -0,0 +1,5 @@
const ContractA = artifacts.require("ContractA");
module.exports = function(deployer) {
deployer.deploy(ContractA);
};

@ -0,0 +1,5 @@
const ContractB = artifacts.require("ContractB");
module.exports = function(deployer) {
deployer.deploy(ContractB);
};

@ -0,0 +1,5 @@
const ContractC = artifacts.require("ContractC");
module.exports = function(deployer) {
deployer.deploy(ContractC);
};

@ -0,0 +1,15 @@
const ContractA = artifacts.require("ContractA");
contract("contracta", function(accounts) {
let instance;
before(async () => instance = await ContractA.deployed())
it('sends', async function(){
await instance.sendFn();
});
it('calls', async function(){
await instance.callFn();
})
});

@ -0,0 +1,15 @@
const ContractB = artifacts.require("ContractB");
contract("contractB", function(accounts) {
let instance;
before(async () => instance = await ContractB.deployed())
it('sends', async function(){
await instance.sendFn();
});
it('calls', async function(){
await instance.callFn();
})
});

@ -0,0 +1,20 @@
const ContractC = artifacts.require("ContractC");
contract("contractc", function(accounts) {
let instance;
before(async () => instance = await ContractC.deployed())
it('sends', async function(){
await instance.sendFn();
});
it('calls', async function(){
await instance.callFn();
})
it('sends', async function(){
await instance.sendFn();
});
});

@ -0,0 +1,99 @@
/**
* Use this file to configure your truffle project. It's seeded with some
* common settings for different networks and features like migrations,
* compilation and testing. Uncomment the ones you need or modify
* them to suit your project as necessary.
*
* More information about configuration can be found at:
*
* truffleframework.com/docs/advanced/configuration
*
* To deploy via Infura you'll need a wallet provider (like truffle-hdwallet-provider)
* to sign your transactions before they're sent to a remote public node. Infura accounts
* are available for free at: infura.io/register.
*
* You'll also need a mnemonic - the twelve word phrase the wallet uses to generate
* public/private key pairs. If you're publishing your code to GitHub make sure you load this
* phrase from a file you've .gitignored so it doesn't accidentally become public.
*
*/
// const HDWalletProvider = require('truffle-hdwallet-provider');
// const infuraKey = "fj4jll3k.....";
//
// const fs = require('fs');
// const mnemonic = fs.readFileSync(".secret").toString().trim();
module.exports = {
/**
* Networks define how you connect to your ethereum client and let you set the
* defaults web3 uses to send transactions. If you don't specify one truffle
* will spin up a development blockchain for you on port 9545 when you
* run `develop` or `test`. You can ask a truffle command to use a specific
* network from the command line, e.g
*
* $ truffle test --network <network-name>
*/
networks: {
// Useful for testing. The `development` name is special - truffle uses it by default
// if it's defined here and no other network is specified at the command line.
// You should run a client (like ganache-cli, geth or parity) in a separate terminal
// tab if you use this network and you must also set the `host`, `port` and `network_id`
// options below to some value.
//
// development: {
// host: "127.0.0.1", // Localhost (default: none)
// port: 8545, // Standard Ethereum port (default: none)
// network_id: "*", // Any network (default: none)
// },
// Another network with more advanced options...
// advanced: {
// port: 8777, // Custom port
// network_id: 1342, // Custom network
// gas: 8500000, // Gas sent with each transaction (default: ~6700000)
// gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei)
// from: <address>, // Account to send txs from (default: accounts[0])
// websockets: true // Enable EventEmitter interface for web3 (default: false)
// },
// Useful for deploying to a public network.
// NB: It's important to wrap the provider as a function.
// ropsten: {
// provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`),
// network_id: 3, // Ropsten's id
// gas: 5500000, // Ropsten has a lower block limit than mainnet
// confirmations: 2, // # of confs to wait between deployments. (default: 0)
// timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50)
// skipDryRun: true // Skip dry run before migrations? (default: false for public nets )
// },
// Useful for private networks
// private: {
// provider: () => new HDWalletProvider(mnemonic, `https://network.io`),
// network_id: 2111, // This network is yours, in the cloud.
// production: true // Treats this network as if it was a public net. (default: false)
// }
},
// Set default mocha options here, use special reporters etc.
mocha: {
// timeout: 100000
},
// Configure your compilers
compilers: {
solc: {
// version: "0.5.1", // Fetch exact version from solc-bin (default: truffle's version)
// docker: true, // Use "0.5.1" you've installed locally with docker (default: false)
// settings: { // See the solidity docs for advice about optimization and evmVersion
// optimizer: {
// enabled: false,
// runs: 200
// },
// evmVersion: "byzantium"
// }
}
}
}

@ -0,0 +1,4 @@
module.exports = {
silent: process.env.SILENT ? true : false,
skipFiles: ['skipped-folder']
}

@ -0,0 +1,17 @@
pragma solidity ^0.5.0;
contract ContractA {
uint x;
constructor() public {
}
function sendFn() public {
x = 5;
}
function callFn() public pure returns (uint){
uint y = 5;
return y;
}
}

@ -0,0 +1,23 @@
pragma solidity >=0.4.21 <0.6.0;
contract Migrations {
address public owner;
uint public last_completed_migration;
constructor() public {
owner = msg.sender;
}
modifier restricted() {
if (msg.sender == owner) _;
}
function setCompleted(uint completed) public restricted {
last_completed_migration = completed;
}
function upgrade(address new_address) public restricted {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}

@ -0,0 +1,17 @@
pragma solidity ^0.5.0;
contract ContractB {
uint x;
constructor() public {
}
function sendFn() public {
x = 5;
}
function callFn() public pure returns (uint){
uint y = 5;
return y;
}
}

@ -0,0 +1,5 @@
const Migrations = artifacts.require("Migrations");
module.exports = function(deployer) {
deployer.deploy(Migrations);
};

@ -0,0 +1,29 @@
const ContractA = artifacts.require("ContractA");
const ContractB = artifacts.require("ContractB");
contract("contracts", function(accounts) {
let instanceA;
let instanceB;
before(async () => {
instanceA = await ContractA.new();
instanceB = await ContractB.new();
});
it('A sends', async function(){
await instanceA.sendFn();
});
it('A calls', async function(){
await instanceA.callFn();
});
it('B sends', async function(){
await instanceB.sendFn();
});
it('B calls', async function(){
await instanceB.callFn();
});
});

@ -0,0 +1,99 @@
/**
* Use this file to configure your truffle project. It's seeded with some
* common settings for different networks and features like migrations,
* compilation and testing. Uncomment the ones you need or modify
* them to suit your project as necessary.
*
* More information about configuration can be found at:
*
* truffleframework.com/docs/advanced/configuration
*
* To deploy via Infura you'll need a wallet provider (like truffle-hdwallet-provider)
* to sign your transactions before they're sent to a remote public node. Infura accounts
* are available for free at: infura.io/register.
*
* You'll also need a mnemonic - the twelve word phrase the wallet uses to generate
* public/private key pairs. If you're publishing your code to GitHub make sure you load this
* phrase from a file you've .gitignored so it doesn't accidentally become public.
*
*/
// const HDWalletProvider = require('truffle-hdwallet-provider');
// const infuraKey = "fj4jll3k.....";
//
// const fs = require('fs');
// const mnemonic = fs.readFileSync(".secret").toString().trim();
module.exports = {
/**
* Networks define how you connect to your ethereum client and let you set the
* defaults web3 uses to send transactions. If you don't specify one truffle
* will spin up a development blockchain for you on port 9545 when you
* run `develop` or `test`. You can ask a truffle command to use a specific
* network from the command line, e.g
*
* $ truffle test --network <network-name>
*/
networks: {
// Useful for testing. The `development` name is special - truffle uses it by default
// if it's defined here and no other network is specified at the command line.
// You should run a client (like ganache-cli, geth or parity) in a separate terminal
// tab if you use this network and you must also set the `host`, `port` and `network_id`
// options below to some value.
//
// development: {
// host: "127.0.0.1", // Localhost (default: none)
// port: 8545, // Standard Ethereum port (default: none)
// network_id: "*", // Any network (default: none)
// },
// Another network with more advanced options...
// advanced: {
// port: 8777, // Custom port
// network_id: 1342, // Custom network
// gas: 8500000, // Gas sent with each transaction (default: ~6700000)
// gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei)
// from: <address>, // Account to send txs from (default: accounts[0])
// websockets: true // Enable EventEmitter interface for web3 (default: false)
// },
// Useful for deploying to a public network.
// NB: It's important to wrap the provider as a function.
// ropsten: {
// provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`),
// network_id: 3, // Ropsten's id
// gas: 5500000, // Ropsten has a lower block limit than mainnet
// confirmations: 2, // # of confs to wait between deployments. (default: 0)
// timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50)
// skipDryRun: true // Skip dry run before migrations? (default: false for public nets )
// },
// Useful for private networks
// private: {
// provider: () => new HDWalletProvider(mnemonic, `https://network.io`),
// network_id: 2111, // This network is yours, in the cloud.
// production: true // Treats this network as if it was a public net. (default: false)
// }
},
// Set default mocha options here, use special reporters etc.
mocha: {
// timeout: 100000
},
// Configure your compilers
compilers: {
solc: {
// version: "0.5.1", // Fetch exact version from solc-bin (default: truffle's version)
// docker: true, // Use "0.5.1" you've installed locally with docker (default: false)
// settings: { // See the solidity docs for advice about optimization and evmVersion
// optimizer: {
// enabled: false,
// runs: 200
// },
// evmVersion: "byzantium"
// }
}
}
}

@ -80,24 +80,25 @@ describe('app', function() {
await plugin(truffleConfig); await plugin(truffleConfig);
}); });
it.skip('with pure and view modifiers and libraries', () => { it('project uses multiple migrations', async function() {
assertCleanInitialState(); assertCleanInitialState();
mock.installFullProject('multiple-migrations');
await plugin(truffleConfig);
});
mock.installDouble(config); it('project skips a folder', async function() {
shell.exec(script); assertCleanInitialState();
assert(shell.error() === null, 'script should not error'); mock.installFullProject('skipping');
await plugin(truffleConfig);
assertCoverageGenerated(); });
// Coverage should be real.
// This test is tightly bound to the function names in TotallyPure.sol
const produced = JSON.parse(fs.readFileSync('./coverage.json', 'utf8'));
const path = Object.keys(produced)[0];
assert(produced[path].fnMap['1'].name === 'usesThem', 'should map "usesThem"');
assert(produced[path].fnMap['2'].name === 'isPure', 'should map "getX"');
it.skip('project with node_modules packages and relative path solidity imports', async function() {
assertCleanInitialState();
mock.installFullProject('import-paths');
await plugin(truffleConfig);
}); });
it('contract only uses ".call"', async function(){ it('contract only uses ".call"', async function(){
assertCleanInitialState(); assertCleanInitialState();

@ -16,6 +16,7 @@ const testPath = './test/sources/js/';
const sourcesPath = './test/sources/solidity/contracts/app/'; const sourcesPath = './test/sources/solidity/contracts/app/';
const migrationPath = `${temp}/migrations/2_deploy.js`; const migrationPath = `${temp}/migrations/2_deploy.js`;
const templatePath = './test/integration/truffle/*'; const templatePath = './test/integration/truffle/*';
const projectPath = './test/integration/projects/'
function getDefaultTruffleConfig(){ function getDefaultTruffleConfig(){
const logger = process.env.SILENT ? { log: () => {} } : console; const logger = process.env.SILENT ? { log: () => {} } : console;
@ -77,6 +78,11 @@ function deployDouble(contractNames){
`; `;
} }
function decacheConfigs(){
decache(`${process.cwd()}/${temp}/.solcover.js`);
decache(`${process.cwd()}/${temp}/${truffleConfigName}`);
}
/** /**
* Installs mock truffle project at ./temp with a single contract * Installs mock truffle project at ./temp with a single contract
* and test specified by the params. * and test specified by the params.
@ -112,8 +118,7 @@ function install(
fs.writeFileSync(`${temp}/${truffleConfigName}`, trufflejs); fs.writeFileSync(`${temp}/${truffleConfigName}`, trufflejs);
fs.writeFileSync(configPath, configjs); fs.writeFileSync(configPath, configjs);
decache(`${process.cwd()}/${temp}/.solcover.js`); decacheConfigs();
decache(`${process.cwd()}/${temp}/${truffleConfigName}`);
}; };
@ -144,10 +149,16 @@ function installDouble(contracts, test, config) {
fs.writeFileSync(`${temp}/${truffleConfigName}`, getTruffleConfigJS()); fs.writeFileSync(`${temp}/${truffleConfigName}`, getTruffleConfigJS());
fs.writeFileSync(configPath, configjs); fs.writeFileSync(configPath, configjs);
decache(`${process.cwd()}/${temp}/.solcover.js`); decacheConfigs();
decache(`${process.cwd()}/${temp}/${truffleConfigName}`);
}; };
function installFullProject(name) {
shell.mkdir(temp);
shell.cp('-Rf', `${projectPath}${name}/{.,}*`, temp);
decacheConfigs();
}
/** /**
* Removes mock truffle project and coverage reports generated by test * Removes mock truffle project and coverage reports generated by test
@ -167,6 +178,7 @@ module.exports = {
getDefaultTruffleConfig: getDefaultTruffleConfig, getDefaultTruffleConfig: getDefaultTruffleConfig,
install: install, install: install,
installDouble: installDouble, installDouble: installDouble,
installFullProject: installFullProject,
clean: clean clean: clean
} }

Loading…
Cancel
Save