Skip to content

Commit

Permalink
Adds some tests for the type generation and refactors to typegenerato…
Browse files Browse the repository at this point in the history
…r code to allow easier unit testing. Also removes `verbose_logging` from the argsv since it wasn't used.
  • Loading branch information
dallasjohnson committed Feb 10, 2020
1 parent 8e90d12 commit 76e8c58
Show file tree
Hide file tree
Showing 4 changed files with 270 additions and 56 deletions.
9 changes: 0 additions & 9 deletions src/cli/lamington-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import { eosIsReady, startEos, runTests, stopContainer, buildAll } from './utils
import { GitIgnoreManager } from '../gitignoreManager';
import { ConfigManager } from '../configManager';

export var verbose_logging: Boolean;

/**
* Executes a build and test procedure
* @note Keep alive setup is incomplete
Expand All @@ -15,13 +13,6 @@ const run = async () => {
await ConfigManager.initWithDefaults();
const args = process.argv;

console.log('args: ' + args);

if (args.includes('verbose')) {
verbose_logging = true;
console.log('`verbose_logging` set to `true`');
}

// Stop running instances for fresh test environment
if (await eosIsReady()) {
await stopContainer();
Expand Down
211 changes: 208 additions & 3 deletions src/contracts/typeGenerator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { assert } from 'chai';

import { ConfigManager } from '../configManager';
import { eosIsReady, startEos, buildAll, stopContainer } from '../cli/utils';
import { mapParameterType } from './typeGenerator';
import { mapParameterType, generateTypesFromString } from './typeGenerator';

/**
* Javascript only supports 64 bit floating point numbers natively, so CPP integer types need to be mapped accordingly
Expand Down Expand Up @@ -91,8 +91,213 @@ describe('type generator', function() {
assert.equal(mapParameterType({ eosType: 'bool[]' }), 'Array<boolean>');
});

xit(`should handle vector types`, function() {
assert.equal(mapParameterType({ eosType: 'vector<string>' }), 'Array<string>');
it(`should handle custom types from the contract`, function() {
assert.equal(
mapParameterType({
eosType: 'pair_uint8_string',
contractName: 'TestContract',
contractStructs: { pair_uint8_string: 'pair_uint8_string' },
}),
'TestContractPairUint8String'
);
});

it(`should handle custom types in containers`, function() {
assert.equal(
mapParameterType({
eosType: 'pair_uint8_string[]',
contractName: 'TestContract',
contractStructs: { pair_uint8_string: 'pair_uint8_string' },
}),
'Array<TestContractPairUint8String>'
);
});
});

context('generator levels for ABI string', async () => {
const rawABI = `
{
"____comment": "This file was generated with eosio-abigen. DO NOT EDIT ",
"version": "eosio::abi/1.1",
"types": [],
"structs": [{
"name": "dac",
"base": "",
"fields": [{
"name": "owner",
"type": "name"
},
{
"name": "dac_id",
"type": "name"
},
{
"name": "title",
"type": "string"
},
{
"name": "symbol",
"type": "extended_symbol"
},
{
"name": "refs",
"type": "pair_uint8_string[]"
},
{
"name": "accounts",
"type": "pair_uint8_name[]"
},
{
"name": "dac_state",
"type": "uint8"
}
]
},
{
"name": "extended_symbol",
"base": "",
"fields": [{
"name": "symbol",
"type": "symbol"
},
{
"name": "contract",
"type": "name"
}
]
},
{
"name": "pair_uint8_name",
"base": "",
"fields": [{
"name": "key",
"type": "uint8"
},
{
"name": "value",
"type": "name"
}
]
},
{
"name": "pair_uint8_string",
"base": "",
"fields": [{
"name": "key",
"type": "uint8"
},
{
"name": "value",
"type": "string"
}
]
},
{
"name": "regaccount",
"base": "",
"fields": [{
"name": "dac_id",
"type": "name"
},
{
"name": "account",
"type": "name"
},
{
"name": "type",
"type": "uint8"
}
]
},
{
"name": "regdac",
"base": "",
"fields": [{
"name": "owner",
"type": "name"
},
{
"name": "dac_id",
"type": "name"
},
{
"name": "dac_symbol",
"type": "extended_symbol"
},
{
"name": "title",
"type": "string"
},
{
"name": "refs",
"type": "pair_uint8_string[]"
},
{
"name": "accounts",
"type": "pair_uint8_name[]"
}
]
}
],
"actions": [{
"name": "regaccount",
"type": "regaccount",
"ricardian_contract": "## ACTION: regaccount**PARAMETERS:*** __dac_name__ is an eosio account name uniquely identifying the DAC. * __account__ is an eosio account name to be associated with the DAC* __type__ a number representing type of the association with the DAC**INTENT:** The intent of regaccount is create a releationship between an eosio account and the DAC for a particular purpose. ####Warning: This action will store the content on the chain in the history logs and the data cannot be deleted later."
},
{
"name": "regdac",
"type": "regdac",
"ricardian_contract": "## ACTION: <regdac>**PARAMETERS:*** __owner__ is an eosio account name for the owner account of the DAC. * __dac_name__ is an eosio account name uniquely identifying the DAC. * __dac_symbol__ is an eosio symbol name representing the primary token used in the DAC. * __title__ is a string that for the title of the DAC.* __refs__* __accounts__ a map of the key accounts used in the DACs**INTENT** The intent of regdac register a new DAC with all the required key accounts #### Warning: This action will store the content on the chain in the history logs and the data cannot be deleted later so therefore should only store a unidentifiable hash of content rather than human readable content."
}
],
"tables": [{
"name": "dacs",
"type": "dac",
"index_type": "i64",
"key_names": [],
"key_types": []
}],
"ricardian_clauses": [{
"id": "ENTIRE AGREEMENT",
"body": "This contract contains the entire agreement of the parties, for all described actions, and there are no other promises or conditions in any other agreement whether oral or written concerning the subject matter of this Contract. This contract supersedes any prior written or oral agreements between the parties."
},
{
"id": "BINDING CONSTITUTION",
"body": "All the the action descibed in this contract are subject to the EOSDAC consitution as held at http://eosdac.io. This includes, but is not limited to membership terms and conditions, dispute resolution and severability."
}
],
"variants": []
}
`;
let result: string[] = [];
before(async () => {
const rawResult = await generateTypesFromString(rawABI, 'TestContractName');
result = rawResult.split('\n');

// Uncomment below to help debug tests
// result.forEach((v, i) => {
// console.log(i + ' ' + JSON.stringify(v));
// });
});

it('should have the correct number of elements', async () => {
assert.equal(result.length, 59);
});
it('should add file header constants', async () => {
assert.equal(result[1], '// WARNING: GENERATED FILE');
});
it('should add Table Row type defs', async () => {
assert.equal(result[9], 'export interface TestContractNameDac {');
assert.equal(result[19], 'export interface TestContractNameExtendedSymbol {');
assert.equal(result[20], '\tsymbol: string;');
});

it('should add Action type defs', async () => {
assert.equal(result[49], 'export interface TestContractName extends Contract {');
assert.equal(
result[51],
'\tregaccount(dac_id: string|number, account: string|number, type: number, options?: { from?: Account, auths?: ActorPermission[] }): Promise<any>;'
);
});
});
});
Expand Down
105 changes: 62 additions & 43 deletions src/contracts/typeGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,37 +42,11 @@ export const mapParameterType = ({
}
};

/**
* Loads all `.abi` files and generates types
* @author Kevin Brown <github.com/thekevinbrown>
*/
export const generateAllTypes = async () => {
// Load all `.abi` files
const files = await glob('**/*.abi');
// Handle no files found
if (files.length === 0) throw new Error('No ABI files to generate from. Exiting.');
// Generate types for each file
for (const file of files) await generateTypes(file);
};

/**
* Generates a Typescript definition file from a contract ABI file
* @author Kevin Brown <github.com/thekevinbrown>
* @author Mitch Pierias <github.com/MitchPierias>
* @param contractIdentifier Path to file without extension
*/
export const generateTypes = async (contractIdentifier: string) => {
// Create contract details
const contractName = path.basename(contractIdentifier);
const abiPath = path.join(
ConfigManager.outDir,
'compiled_contracts',
`${contractIdentifier}.abi`
);
// Handle ABI file loading
if (!fs.existsSync(path.resolve(abiPath)))
throw new Error(`Missing ABI file at path '${path.resolve(abiPath)}'`);
const abi = JSON.parse(fs.readFileSync(path.resolve(abiPath), 'utf8'));
export const generateTypesFromString = async (
rawABI: string,
contractName: string
): Promise<string> => {
const abi = JSON.parse(rawABI);
let contractActions = abi.actions;
let contractTables = abi.tables;
let contractStructs = Object.assign(
Expand Down Expand Up @@ -155,25 +129,51 @@ export const generateTypes = async (contractIdentifier: string) => {
// Cache contract result
result.push(contractInterface);
result.push('');
// Save generated contract
await saveInterface(contractIdentifier, result);
return flattenGeneratorLevels(result);
};

/**
* Writes the contract interface to file
* Loads all `.abi` files and generates types
* @author Kevin Brown <github.com/thekevinbrown>
*/
export const generateAllTypes = async () => {
// Load all `.abi` files
const files = await glob('**/*.abi');
// Handle no files found
if (files.length === 0) throw new Error('No ABI files to generate from. Exiting.');
// Generate types for each file
for (const file of files) await generateTypes(file);
};

/**
* Generates a Typescript definition file from a contract ABI file
* @author Kevin Brown <github.com/thekevinbrown>
* @author Mitch Pierias <github.com/MitchPierias>
* @author Dallas Johnson <github.com/dallasjohnson>
* @param contractIdentifier Path to file without extension
* @param interfaceContent Generated contract interface
*/
const saveInterface = async (
contractIdentifier: string,
interfaceContent: GeneratorLevel | IndentedGeneratorLevel
) => {
// Open a write stream to file
const file = fs.createWriteStream(`${contractIdentifier}.ts`);
// Write formatted blocks
export const generateTypes = async (contractIdentifier: string) => {
// Create contract details
const contractName = path.basename(contractIdentifier);
const abiPath = path.join(
ConfigManager.outDir,
'compiled_contracts',
`${contractIdentifier}.abi`
);
// Handle ABI file loading
if (!fs.existsSync(path.resolve(abiPath)))
throw new Error(`Missing ABI file at path '${path.resolve(abiPath)}'`);
const rawABI = fs.readFileSync(path.resolve(abiPath), 'utf8');

const generaterLevels = await generateTypesFromString(rawABI, contractName);
await saveInterface(contractIdentifier, generaterLevels);
};

const flattenGeneratorLevels = (interfaceContent: GeneratorLevel): string => {
let result = '';
let indentLevel = 0;
const write = (value: string) => file.write('\t'.repeat(indentLevel) + value + '\n');
const write = (value: string) => (result += '\t'.repeat(indentLevel) + value + '\n');
const writeIndented = (level: IndentedGeneratorLevel) => {
for (const outerWrapper of Object.keys(level)) {
write(`${outerWrapper} {`);
Expand Down Expand Up @@ -201,5 +201,24 @@ const saveInterface = async (
};
// Write interface to file and close
writeLevel(interfaceContent);
return result;
};

/**
* Writes the contract interface to file
* @author Kevin Brown <github.com/thekevinbrown>
* @author Dallas Johnson <github.com/dallasjohnson>
* @param contractIdentifier Path to file without extension
* @param interfaceContent Generated contract interface as a string
*/
const saveInterface = async (contractIdentifier: string, interfaceContent: string) => {
// Open a write stream to file
const file = fs.createWriteStream(`${contractIdentifier}.ts`);

file.write(interfaceContent, error => {
if (error) {
throw error;
}
});
file.close();
};
1 change: 0 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import * as chai from 'chai';
import * as deepEqualInAnyOrder from 'deep-equal-in-any-order';

import { EOSManager } from './eosManager';
import { verbose_logging } from './cli/lamington-test';
import { TableRowsResult } from './contracts';
import { ConfigManager } from './configManager';
import * as chalk from 'chalk';
Expand Down

0 comments on commit 76e8c58

Please sign in to comment.