From 042a99e638f99bfd70d8b3b116a6335d5087759b Mon Sep 17 00:00:00 2001 From: cgewecke Date: Fri, 9 Feb 2024 13:51:52 -0800 Subject: [PATCH] Add try / catch unit tests (#857) --- .../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 ++++++++++++++++++ 6 files changed, 210 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 0000000..4a7a752 --- /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 0000000..27c6b23 --- /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 0000000..0003d38 --- /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 0000000..006c2b4 --- /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 0000000..0e54aad --- /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 0000000..93b59a0 --- /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