From 379085d7a8236d930e0f81211a82847a165a18a0 Mon Sep 17 00:00:00 2001 From: cgewecke Date: Fri, 9 Feb 2024 13:32:23 -0800 Subject: [PATCH 1/2] Add try/catch unit tests --- .../contracts/try/try-catch-empty-blocks.sol | 11 ++ .../contracts/try/try-error-block.sol | 16 +++ .../contracts/try/try-multi-block.sol | 29 +++++ .../contracts/try/try-panic-block.sol | 16 +++ .../contracts/try/try-revert-block.sol | 15 +++ test/units/try.js | 123 ++++++++++++++++++ test/util/util.js | 3 + 7 files changed, 213 insertions(+) create mode 100644 test/sources/solidity/contracts/try/try-catch-empty-blocks.sol create mode 100644 test/sources/solidity/contracts/try/try-error-block.sol create mode 100644 test/sources/solidity/contracts/try/try-multi-block.sol create mode 100644 test/sources/solidity/contracts/try/try-panic-block.sol create mode 100644 test/sources/solidity/contracts/try/try-revert-block.sol create mode 100644 test/units/try.js diff --git a/test/sources/solidity/contracts/try/try-catch-empty-blocks.sol b/test/sources/solidity/contracts/try/try-catch-empty-blocks.sol new file mode 100644 index 00000000..4a7a752a --- /dev/null +++ b/test/sources/solidity/contracts/try/try-catch-empty-blocks.sol @@ -0,0 +1,11 @@ +pragma solidity >=0.8.0 <0.9.0; + +contract Test { + function op(bool y) external { + require(y); + } + + function a(bool x) external { + try this.op(x) { } catch { } + } +} diff --git a/test/sources/solidity/contracts/try/try-error-block.sol b/test/sources/solidity/contracts/try/try-error-block.sol new file mode 100644 index 00000000..27c6b234 --- /dev/null +++ b/test/sources/solidity/contracts/try/try-error-block.sol @@ -0,0 +1,16 @@ +pragma solidity >=0.8.0 <0.9.0; + +contract Test { + function op(bool y) external returns (uint){ + require(y, "sorry"); + return 1; + } + + function a(bool x) external returns (uint) { + try this.op(x) returns (uint v) { + return v; + } catch Error(string memory /*reason*/) { + return 0; + } + } +} diff --git a/test/sources/solidity/contracts/try/try-multi-block.sol b/test/sources/solidity/contracts/try/try-multi-block.sol new file mode 100644 index 00000000..0003d38a --- /dev/null +++ b/test/sources/solidity/contracts/try/try-multi-block.sol @@ -0,0 +1,29 @@ +pragma solidity >=0.8.0 <0.9.0; + +contract Test { + function op(uint y) external returns (uint){ + uint a = 100; + uint b = 0; + + if (y == 0) + return 0; + if (y == 1) + require(false, 'sorry'); + if (y == 2) + uint x = (a / b); + if (y == 3) + revert(); + } + + function a(uint x) external returns (uint) { + try this.op(x) returns (uint v) { + return 0; + } catch Error(string memory /*reason*/) { + return 1; + } catch Panic(uint /*errorCode*/) { + return 2; + } catch (bytes memory /*lowLevelData*/) { + return 3; + } + } +} diff --git a/test/sources/solidity/contracts/try/try-panic-block.sol b/test/sources/solidity/contracts/try/try-panic-block.sol new file mode 100644 index 00000000..006c2b4f --- /dev/null +++ b/test/sources/solidity/contracts/try/try-panic-block.sol @@ -0,0 +1,16 @@ +pragma solidity >=0.8.0 <0.9.0; + +contract Test { + function op(bool y) external returns (uint){ + uint x = 100 / 0; + return x; + } + + function a(bool x) external pure returns (uint) { + try this.op(x) returns (uint v) { + return v; + } catch Panic(uint /*errorCode*/) { + return 0; + } + } +} diff --git a/test/sources/solidity/contracts/try/try-revert-block.sol b/test/sources/solidity/contracts/try/try-revert-block.sol new file mode 100644 index 00000000..0e54aad4 --- /dev/null +++ b/test/sources/solidity/contracts/try/try-revert-block.sol @@ -0,0 +1,15 @@ +pragma solidity >=0.8.0 <0.9.0; + +contract Test { + function op(bool y) external returns (uint){ + if (y == false) revert(); + } + + function a(bool x) external pure returns (uint) { + try this.op(x) returns (uint v) { + return v; + } catch (bytes memory /*lowLevelData*/) { + return 0; + } + } +} diff --git a/test/units/try.js b/test/units/try.js new file mode 100644 index 00000000..93b59a03 --- /dev/null +++ b/test/units/try.js @@ -0,0 +1,123 @@ +const assert = require('assert'); +const util = require('./../util/util.js'); +const Coverage = require('./../../lib/coverage'); +const Api = require('./../../lib/api') + +describe.only('try / catch branches', () => { + let coverage; + let api; + + before(async () => api = new Api({silent: true})); + beforeEach(() => coverage = new Coverage()); + after(async() => await api.finish()); + + it('should cover a try/catch statement with empty blocks (success branch only)', async function() { + const contract = await util.bootstrapCoverage('try/try-catch-empty-blocks', api, this.provider, ); + coverage.addContract(contract.instrumented, util.filePath); + await contract.instance.a(true, contract.gas); + const mapping = coverage.generate(contract.data, util.pathPrefix); + + assert.deepEqual(mapping[util.filePath].l, { + 5:1,9:1 + }); + assert.deepEqual(mapping[util.filePath].b, { + 1:[1,0] + }); + assert.deepEqual(mapping[util.filePath].s, { + 1:1,2:1 + }); + assert.deepEqual(mapping[util.filePath].f, { + 1:1,2:1 + }); + }); + + it('should cover a try/catch statement with empty blocks (both branches)', async function() { + const contract = await util.bootstrapCoverage('try/try-catch-empty-blocks', api, this.provider, ); + coverage.addContract(contract.instrumented, util.filePath); + await contract.instance.a(true, contract.gas); + + try { await contract.instance.a(false, contract.gas) } catch(err) { /* ignore */ } + + const mapping = coverage.generate(contract.data, util.pathPrefix); + + assert.deepEqual(mapping[util.filePath].l, { + 5:2,9:2 + }); + assert.deepEqual(mapping[util.filePath].b, { + 1:[1,1] + }); + assert.deepEqual(mapping[util.filePath].s, { + 1:2,2:2 + }); + assert.deepEqual(mapping[util.filePath].f, { + 1:2,2:2 + }); + }); + + it('should cover a try/catch statement with an Error block (success branch only)', async function() { + const contract = await util.bootstrapCoverage('try/try-error-block', api, this.provider, ); + coverage.addContract(contract.instrumented, util.filePath); + await contract.instance.a(true, contract.gas); + + const mapping = coverage.generate(contract.data, util.pathPrefix); + + assert.deepEqual(mapping[util.filePath].l, { + 5:1,6:1,10:1,11:1,13:0 + }); + assert.deepEqual(mapping[util.filePath].b, { + 1:[1,0] + }); + assert.deepEqual(mapping[util.filePath].s, { + 1:1,2:1,3:1,4:1,5:0 + }); + assert.deepEqual(mapping[util.filePath].f, { + 1:1,2:1 + }); + }); + + it('should cover a try/catch statement with an Error block (both branches)', async function() { + const contract = await util.bootstrapCoverage('try/try-error-block', api, this.provider, ); + coverage.addContract(contract.instrumented, util.filePath); + + await contract.instance.a(true, contract.gas); + try { await contract.instance.a(false, contract.gas) } catch(err) { /* ignore */ } + + const mapping = coverage.generate(contract.data, util.pathPrefix); + + assert.deepEqual(mapping[util.filePath].l, { + 5:2,6:1,10:2,11:1,13:1 + }); + assert.deepEqual(mapping[util.filePath].b, { + 1:[1,1] + }); + assert.deepEqual(mapping[util.filePath].s, { + 1:2,2:1,3:2,4:1,5:1 + }); + assert.deepEqual(mapping[util.filePath].f, { + 1:2,2:2 + }); + }); + + it('should cover a try/catch statement with multi-block catch clauses (middle-block)', async function() { + const contract = await util.bootstrapCoverage('try/try-multi-block', api, this.provider, ); + coverage.addContract(contract.instrumented, util.filePath); + + await contract.instance.a(0, contract.gas); + try { await contract.instance.a(2, contract.gas) } catch(err) { /* ignore */ } + + const mapping = coverage.generate(contract.data, util.pathPrefix); + + assert.deepEqual(mapping[util.filePath].l, { + 5:2,6:2,8:2,9:1,10:1,11:0,12:1,13:1,14:0,15:0,19:2,20:1,22:0,24:1,26:0 + }); + assert.deepEqual(mapping[util.filePath].b, { + 1:[1,1],2:[0,1],3:[0,0],4:[1,0],5:[0,0] + }); + assert.deepEqual(mapping[util.filePath].s, { + 1:2,2:2,3:2,4:1,5:1,6:0,7:1,8:1,9:0,10:0,11:2,12:1,13:0,14:1,15:0 + }); + assert.deepEqual(mapping[util.filePath].f, { + 1:2,2:2 + }); + }); +}); \ No newline at end of file diff --git a/test/util/util.js b/test/util/util.js index 05f44e16..879cf119 100644 --- a/test/util/util.js +++ b/test/util/util.js @@ -119,6 +119,9 @@ function report(output=[]) { async function bootstrapCoverage(file, api, provider){ const info = instrumentAndCompile(file, api); + const { inspect } = require("util"); + //console.log('solc: ' + inspect(info.solcOutput)); + // Need to define a gasLimit for contract calls because otherwise ethers will estimateGas // and cause duplicate hits for everything info.gas = { gasLimit: 2_000_000 } From c3a64304cc5fa4a28db487366e47c8cb2141003c Mon Sep 17 00:00:00 2001 From: cgewecke Date: Fri, 9 Feb 2024 13:37:12 -0800 Subject: [PATCH 2/2] Remove log lines --- test/util/util.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/util/util.js b/test/util/util.js index 879cf119..05f44e16 100644 --- a/test/util/util.js +++ b/test/util/util.js @@ -119,9 +119,6 @@ function report(output=[]) { async function bootstrapCoverage(file, api, provider){ const info = instrumentAndCompile(file, api); - const { inspect } = require("util"); - //console.log('solc: ' + inspect(info.solcOutput)); - // Need to define a gasLimit for contract calls because otherwise ethers will estimateGas // and cause duplicate hits for everything info.gas = { gasLimit: 2_000_000 }