Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds negative check to event emittion #104

Merged
merged 7 commits into from
Dec 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## 0.5.5 (unreleased)
* Added function `advanceBlockTo`. ([#94](https://github.com/OpenZeppelin/openzeppelin-test-helpers/pull/94))
* Added `expectEvent.not` support to test negative cases. ([#104](https://github.com/OpenZeppelin/openzeppelin-test-helpers/pull/104))

## 0.5.4 (2019-11-13)
* Fixed some RPC calls not having an `id` field. ([#92](https://github.com/OpenZeppelin/openzeppelin-test-helpers/pull/92))
Expand Down
1 change: 1 addition & 0 deletions contracts/EventEmitter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import "./IndirectEventEmitter.sol";

contract EventEmitter {
event Argumentless();
event WillNeverBeEmitted();
event ShortUint(uint8 value);
event ShortInt(int8 value);
event LongUint(uint256 value);
Expand Down
37 changes: 37 additions & 0 deletions docs/modules/ROOT/pages/api.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,43 @@ async function expectEvent.inConstruction(emitter, eventName, eventArgs = {})

Same as `inTransaction`, but for events emitted during the construction of `emitter`. Note that this is currently only supported for truffle contracts.

=== `not.inTransaction`

```javascript
async function notInTransaction (txHash, emitter, eventName)
```

Asserts that the event with name `eventName` as declared in the `emitter` contract was not emitted in the transaction with hash `txHash`. Note that the assertion will also fail if the transaction was emitted indirectly (in a nested external function call).

```javascript
// With web3 contracts
const contract = await MyContract.deploy().send();
const { transactionHash } = await contract.methods.foo().send();
await expectEvent.not.inTransaction(transactionHash, contract, 'NotEmittedByFoo');

// With web3 contracts
const contract = await MyContract.deploy().send();
const { transactionHash } = await contract.methods.foo().send();
await expectEvent.not.inTransaction(transactionHash, contract, 'EmittedByFoo'); // Will fail

// With truffle contracts
const contract = await MyContract.new();
const { txHash } = await contract.foo();
await expectEvent.not.inTransaction(txHash, contract, 'NotEmittedByFoo');

const contract = await MyContract.new();
const { txHash } = await contract.foo();
await expectEvent.not.inTransaction(txHash, contract, 'EmittedByFoo'); // Will fail
```

=== `not.inConstruction`

```javascript
async function notInConstruction (contract, eventName)
```

Same as `not.inTransaction`, but for events emitted during the construction of emitter. Note that this is currently only supported for truffle contracts.

[[expect-revert]]
== `expectRevert`

Expand Down
21 changes: 21 additions & 0 deletions src/expectEvent.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,30 @@ async function inConstruction (contract, eventName, eventArgs = {}) {
return inTransaction(contract.transactionHash, contract.constructor, eventName, eventArgs);
}

async function notInConstruction (contract, eventName) {
if (!isTruffleContract(contract)) {
throw new Error('expectEvent.inConstruction is only supported for truffle-contract objects');
}
return notInTransaction(contract.transactionHash, contract.constructor, eventName);
}

async function inTransaction (txHash, emitter, eventName, eventArgs = {}) {
const receipt = await web3.eth.getTransactionReceipt(txHash);

const logs = decodeLogs(receipt.logs, emitter, eventName);
return inLogs(logs, eventName, eventArgs);
}

async function notInTransaction (txHash, emitter, eventName) {
const receipt = await web3.eth.getTransactionReceipt(txHash);

const logs = decodeLogs(receipt.logs, emitter, eventName);

const events = logs.filter(e => e.event === eventName);

expect(events.length === 0).to.equal(true, `Event ${eventName} was found`);
}

// This decodes longs for a single event type, and returns a decoded object in
// the same form truffle-contract uses on its receipts
function decodeLogs (logs, emitter, eventName) {
Expand Down Expand Up @@ -138,4 +155,8 @@ function isTruffleContract (contract) {
expectEvent.inLogs = deprecate(inLogs, 'expectEvent.inLogs() is deprecated. Use expectEvent() instead.');
expectEvent.inConstruction = inConstruction;
expectEvent.inTransaction = inTransaction;

expectEvent.not = {};
expectEvent.not.inConstruction = notInConstruction;
expectEvent.not.inTransaction = notInTransaction;
module.exports = expectEvent;
59 changes: 59 additions & 0 deletions test/src/expectEvent.truffle.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -458,4 +458,63 @@ contract('expectEvent (truffle contracts)', function ([deployer]) {
});
});
});

describe('not', function () {
describe('inTransaction', function () {
context('with no arguments', function () {
beforeEach(async function () {
const { receipt } = await this.emitter.emitArgumentless();
this.txHash = receipt.transactionHash;
});
it('accepts not emitted events', async function () {
await expectEvent.not.inTransaction(this.txHash, EventEmitter, 'WillNeverBeEmitted');
});
it('throws when event does not exist in ABI', async function () {
await assertFailure(expectEvent.not.inTransaction(this.txHash, EventEmitter, 'Nonexistant'));
});
it('throws when event its emitted', async function () {
await assertFailure(expectEvent.not.inTransaction(this.txHash, EventEmitter, 'Argumentless'));
});
});
context('with arguments', function () {
beforeEach(async function () {
this.value = 42;
const { receipt } = await this.emitter.emitShortUint(this.value);
this.txHash = receipt.transactionHash;
});
it('accepts not emitted events', async function () {
await expectEvent.not.inTransaction(this.txHash, EventEmitter, 'WillNeverBeEmitted');
});
it('throws when event its emitted', async function () {
await assertFailure(expectEvent.not.inTransaction(this.txHash, EventEmitter, 'ShortUint'));
});
});
context('with events emitted by an indirectly called contract', function () {
beforeEach(async function () {
this.value = 'OpenZeppelin';
const { receipt } = await this.emitter.emitStringAndEmitIndirectly(this.value, this.secondEmitter.address);
this.txHash = receipt.transactionHash;
});
it('accepts not emitted events', async function () {
await expectEvent.not.inTransaction(this.txHash, EventEmitter, 'WillNeverBeEmitted');
});
it('throws when event its emitted', async function () {
await assertFailure(expectEvent.not.inTransaction(this.txHash, IndirectEventEmitter, 'IndirectString'));
});
});
});
describe('inConstructor', function () {
it('accepts not emitted events', async function () {
await expectEvent.not.inConstruction(this.emitter, 'WillNeverBeEmitted');
});
it('throws when event does not exist in ABI', async function () {
await assertFailure(expectEvent.not.inConstruction(this.emitter, 'Nonexistant'));
});
it('throws when event its emitted', async function () {
await assertFailure(expectEvent.not.inConstruction(this.emitter, 'ShortUint'));
await assertFailure(expectEvent.not.inConstruction(this.emitter, 'Boolean'));
await assertFailure(expectEvent.not.inConstruction(this.emitter, 'String'));
});
});
});
});
50 changes: 50 additions & 0 deletions test/src/expectEvent.web3.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -430,4 +430,54 @@ contract('expectEvent (web3 contracts) ', function ([deployer]) {
await assertFailure(expectEvent.inConstruction(this.emitter, 'ShortUint'));
});
});

describe('not', function () {
describe('inTransaction', function () {
context('with no arguments', function () {
beforeEach(async function () {
({ transactionHash: this.txHash } = await this.emitter.methods.emitArgumentless().send());
});
it('accepts not emitted events', async function () {
await expectEvent.not.inTransaction(this.txHash, EventEmitter, 'WillNeverBeEmitted');
});
it('throws when event does not exist in ABI', async function () {
await assertFailure(expectEvent.not.inTransaction(this.txHash, EventEmitter, 'Nonexistant'));
});
it('throws when event its emitted', async function () {
await assertFailure(expectEvent.not.inTransaction(this.txHash, EventEmitter, 'Argumentless'));
});
});
context('with arguments', function () {
beforeEach(async function () {
this.value = 42;
({ transactionHash: this.txHash } = await this.emitter.methods.emitShortUint(this.value).send());
});
it('accepts not emitted events', async function () {
await expectEvent.not.inTransaction(this.txHash, EventEmitter, 'WillNeverBeEmitted');
});
it('throws when event its emitted', async function () {
await assertFailure(expectEvent.not.inTransaction(this.txHash, EventEmitter, 'ShortUint'));
});
});
context('with events emitted by an indirectly called contract', function () {
beforeEach(async function () {
this.value = 'OpenZeppelin';
({ transactionHash: this.txHash } = await this.emitter.methods.emitStringAndEmitIndirectly(
this.value, this.secondEmitter.options.address
).send());
});
it('accepts not emitted events', async function () {
await expectEvent.not.inTransaction(this.txHash, EventEmitter, 'WillNeverBeEmitted');
});
it('throws when event its emitted', async function () {
await assertFailure(expectEvent.not.inTransaction(this.txHash, IndirectEventEmitter, 'IndirectString'));
});
});
});
describe('inConstruction', function () {
it('is unsupported', async function () {
await assertFailure(expectEvent.not.inConstruction(this.emitter, 'ShortUint'));
});
});
});
});