-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Document integration tests in implementation guide
- Loading branch information
Showing
9 changed files
with
534 additions
and
0 deletions.
There are no files selected for viewing
79 changes: 79 additions & 0 deletions
79
packages/web/docs/implementation-guides/pointers/testing/TestCase.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import { useState, useEffect } from "react"; | ||
|
||
import prettier from "prettier/standalone"; | ||
|
||
import { describeSchema } from "@ethdebug/format"; | ||
import { observeTraceTests } from "@ethdebug/pointers/dist/src/test-cases" | ||
|
||
import { Collapsible } from "@theme/JSONSchemaViewer/components"; | ||
import CodeBlock from "@theme/CodeBlock"; | ||
import CodeListing from "@site/src/components/CodeListing"; | ||
|
||
const solidityPlugin: any = require("prettier-plugin-solidity/standalone"); | ||
|
||
export interface TestCaseProps { | ||
name: string; | ||
variableName: string; | ||
} | ||
|
||
export default function TestCase({ | ||
name, | ||
variableName | ||
}: TestCaseProps): JSX.Element { | ||
const { | ||
pointer, | ||
compileOptions, | ||
expectedValues | ||
} = observeTraceTests[name as keyof typeof observeTraceTests]; | ||
|
||
const [sourcePath, { content }] = | ||
// use the first since all test cases use only one source file | ||
Object.entries(compileOptions.sources)[0]; | ||
|
||
const [ | ||
formattedContent, | ||
setFormattedContent | ||
] = useState<string | undefined>(); | ||
|
||
useEffect(() => { | ||
prettier.format(content, { | ||
parser: "solidity-parse", | ||
plugins: [solidityPlugin] | ||
}) | ||
.then(setFormattedContent); | ||
}, [setFormattedContent]); | ||
|
||
return <> | ||
<h3>Solidity code</h3> | ||
|
||
{typeof formattedContent === "undefined" | ||
? <>Loading Solidity code...</> | ||
: <CodeBlock language="solidity">{formattedContent}</CodeBlock>} | ||
|
||
<h3>Expected value sequence</h3> | ||
|
||
<ol> | ||
{expectedValues.map((expectedValue, index) => <li key={index}><code>{ | ||
JSON.stringify(expectedValue) | ||
}</code></li>)} | ||
</ol> | ||
|
||
<h3>Pointer</h3> | ||
<Collapsible summary="See Pointer JSON"> | ||
<CodeBlock language="json">{ | ||
JSON.stringify(pointer, undefined, 2) | ||
}</CodeBlock> | ||
</Collapsible> | ||
|
||
<h3>Full test case code listing</h3> | ||
<Collapsible summary="See TypeScript test case definition"> | ||
<CodeListing | ||
packageName="@ethdebug/pointers" | ||
sourcePath="src/test-cases.ts" | ||
extract={sourceFile => sourceFile.getVariableStatement(variableName)} | ||
/> | ||
</Collapsible> | ||
|
||
|
||
</>; | ||
} |
8 changes: 8 additions & 0 deletions
8
packages/web/docs/implementation-guides/pointers/testing/_category_.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"label": "End-to-end testing", | ||
"position": 6, | ||
"link": { | ||
"type": "generated-index", | ||
"description": "Implementing integration tests" | ||
} | ||
} |
174 changes: 174 additions & 0 deletions
174
packages/web/docs/implementation-guides/pointers/testing/blockchain-simulation.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
--- | ||
sidebar_position: 4 | ||
--- | ||
|
||
import CodeListing from "@site/src/components/CodeListing"; | ||
|
||
# Simulating a blockchain | ||
|
||
:::warning | ||
|
||
In case you missed the | ||
[note on the Summary page](/docs/implementation-guides/pointers/testing/summary#ganache-warning), | ||
the functionality described in this page uses the unmaintained | ||
[Ganache](https://github.com/trufflesuite/ganache) software library for | ||
simulating the EVM. See note for rationale and risk expectations. | ||
|
||
::: | ||
|
||
This reference implemention relies heavily on the | ||
[`Machine`](/docs/implementation-guides/pointers/types/data-and-machines#Machine) | ||
interface it defines for reading the state of a running EVM; this page describes | ||
how this implementation's integration tests adapt an | ||
[EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) JavaScript provider object | ||
to this interface. | ||
|
||
Since the primary purpose of `Machine` is to represent a series of code | ||
execution steps, the adapter described here simplifies the concept of an | ||
execution trace by restricting it to mean that which happens within the course | ||
of an Ethereum transaction. The tests thus define a `machineForProvider` | ||
function to adapt a provider object for a particular transaction hash. | ||
|
||
As a result, this code only functions in the context of a provider to a | ||
blockchain whose JSON-RPC exposes the original | ||
[go-ethereum](https://github.com/ethereum/go-ethereum)'s | ||
`"debug_traceTransaction"` method, which exposes the state of the EVM at each | ||
step of code execution for a particular transaction. Other kinds of traces (such | ||
as tracing the execution of an `"eth_call"` request) are left to remain | ||
intentionally out-of-scope for the purposes of testing this implementation. | ||
Other implementations of the `Machine` interface need not make this restriction. | ||
|
||
## Implementing `machineForProvider()` | ||
|
||
The `machineForProvider()` function takes two arguments and returns an object | ||
adhering to the `Machine` interface. See the code listing for this function: | ||
|
||
<CodeListing | ||
packageName="@ethdebug/pointers" | ||
sourcePath="test/ganache.ts" | ||
extract={(sourceFile) => sourceFile.getFunction("machineForProvider")} | ||
/> | ||
|
||
This function is written to return an object whose `trace()` method matches that | ||
which is defined by `Machine`: a method to asynchronously produce an iterable | ||
list of `Machine.State`s. This function leverages two other helper functions as | ||
part of the behavior of this method: `requestStructLogs()` and | ||
`toMachineState()`. | ||
|
||
### Requesting "struct logs" | ||
|
||
The Geth-style `"debug_traceTransaction"` method returns a list of execution | ||
steps and machine states inside the `"structLogs"` field of the response's | ||
result object. | ||
|
||
The asynchronous `requestStructLogs` function is implemented as follows: | ||
|
||
<CodeListing | ||
packageName="@ethdebug/pointers" | ||
sourcePath="test/ganache.ts" | ||
extract={(sourceFile) => sourceFile.getFunction("requestStructLogs")} | ||
/> | ||
|
||
Since Ganache does not have a publicly-documented or easily-accessible exported | ||
collection of types, but since it **does** use string literal types to infer the | ||
specific type of provider request being made, this code can use TypeScript's | ||
type interference to ensure type safety in the adapter: | ||
|
||
<CodeListing | ||
packageName="@ethdebug/pointers" | ||
sourcePath="test/ganache.ts" | ||
extract={(sourceFile) => sourceFile.getTypeAlias("StructLogs")} | ||
/> | ||
|
||
<CodeListing | ||
packageName="@ethdebug/pointers" | ||
sourcePath="test/ganache.ts" | ||
extract={(sourceFile) => sourceFile.getTypeAlias("StructLog")} | ||
/> | ||
|
||
<CodeListing | ||
packageName="@ethdebug/pointers" | ||
sourcePath="test/ganache.ts" | ||
extract={(sourceFile) => sourceFile.getTypeAlias("Depromise")} | ||
/> | ||
|
||
<CodeListing | ||
packageName="@ethdebug/pointers" | ||
sourcePath="test/ganache.ts" | ||
extract={(sourceFile) => sourceFile.getTypeAlias("Dearray")} | ||
/> | ||
|
||
These types are not exported by this module because they are internal to | ||
`machineForProvider()` concerns. | ||
|
||
### Converting to `Machine.State` | ||
|
||
The `toMachineState()` function is implemented by leveraging the use of the | ||
[addressing schemes](/spec/pointer/concepts#a-region-is-specified-in-terms-of-an-addressing-scheme) | ||
defined by the **ethdebug/format/pointer** schema. Notice the use of the various | ||
helper functions, listed below. | ||
|
||
<CodeListing | ||
packageName="@ethdebug/pointers" | ||
sourcePath="test/ganache.ts" | ||
extract={(sourceFile) => sourceFile.getFunction("toMachineState")} | ||
/> | ||
|
||
#### Helper function: `constantUint()` | ||
|
||
Since the interface defined by `Machine.State` is more asynchronous than likely | ||
necessary (certainly it is more asynchronous than necessary for these testing | ||
purposes), many properties defined within `Machine.State` must be converted from | ||
a readily-available constant value into a `Promise` that resolves to that value: | ||
|
||
<CodeListing | ||
packageName="@ethdebug/pointers" | ||
sourcePath="test/ganache.ts" | ||
extract={(sourceFile) => sourceFile.getFunction("constantUint")} | ||
/> | ||
|
||
#### Helper function: `makeStack()` | ||
|
||
Although the specification defines the `"stack"` data location to use a regular | ||
segment-based addressing scheme, this reference implementation distinguishes the | ||
stack from the other segment-based locations because of the use of numeric, | ||
unstable slot values. | ||
|
||
<CodeListing | ||
packageName="@ethdebug/pointers" | ||
sourcePath="test/ganache.ts" | ||
extract={(sourceFile) => sourceFile.getFunction("makeStack")} | ||
/> | ||
|
||
#### Helper function: `makeWords()` | ||
|
||
For other segment-based locations, the `makeWords()` function is used: | ||
|
||
<CodeListing | ||
packageName="@ethdebug/pointers" | ||
sourcePath="test/ganache.ts" | ||
extract={(sourceFile) => sourceFile.getFunction("makeWords")} | ||
/> | ||
|
||
#### Helper function: `makeBytes()` | ||
|
||
The `makeBytes()` function is used for plain bytes-based data locations, such as | ||
`"memory"`: | ||
|
||
<CodeListing | ||
packageName="@ethdebug/pointers" | ||
sourcePath="test/ganache.ts" | ||
extract={(sourceFile) => sourceFile.getFunction("makeBytes")} | ||
/> | ||
|
||
## Note on loading Ganache | ||
|
||
To prevent Ganache's warnings from appearing in test console output, a custom | ||
`loadGanache()` function is defined to suppress known warnings while importing | ||
the module: | ||
|
||
<CodeListing | ||
packageName="@ethdebug/pointers" | ||
sourcePath="test/ganache.ts" | ||
extract={(sourceFile) => sourceFile.getFunction("loadGanache")} | ||
/> |
54 changes: 54 additions & 0 deletions
54
packages/web/docs/implementation-guides/pointers/testing/compilation.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
--- | ||
sidebar_position: 3 | ||
--- | ||
|
||
import CodeListing from "@site/src/components/CodeListing"; | ||
|
||
# Invoking the compiler | ||
|
||
In being able to test a pointer dereference implementation, it is necessary to | ||
pair each tested pointer with associated EVM code that makes the pointer | ||
meaningful. To avoid solutions such as pre-compiling Solidity or handwriting EVM | ||
bytecode, the **@ethdebug/pointers** reference implementation's integration | ||
tests are written so that each test case is described in terms of Solidity code | ||
that the testing infrastructure compiles when executing the test. | ||
|
||
The strategy taken by these tests is to use Solidity's `constructor` mechanism | ||
for allowing tests to specify variable assignment and mutation logic without | ||
needing to manage deployed contract instances. All these integration test cases | ||
thus observe pointers only via the trace of a contract creation transaction. | ||
|
||
## Integration logic | ||
|
||
This testing infrastructure includes the `compileCreateBytecode()` function, | ||
which accepts input resembling Solidity's compiler input data as argument (i.e., | ||
the collection of source contents by path and additional contract target | ||
information) and asynchronously returns `Data` with the create (deployment) | ||
bytecode for the target contract. | ||
|
||
<CodeListing | ||
packageName="@ethdebug/pointers" | ||
sourcePath="test/solc.ts" | ||
extract={(sourceFile) => sourceFile.getFunction("compileCreateBytecode")} | ||
/> | ||
|
||
## The `CompileOptions` interface | ||
|
||
<CodeListing | ||
packageName="@ethdebug/pointers" | ||
sourcePath="test/solc.ts" | ||
extract={(sourceFile) => sourceFile.getInterface("CompileOptions")} | ||
/> | ||
|
||
## "Syntactic sugar"-like helper function | ||
|
||
To avoid test cases' needing to describe their associated code samples in terms | ||
of source content by path, test cases that require only a single source file can | ||
use the `singleSourceCompilation()` helper function that provides a more | ||
succinct method for generating `CompileOptions` objects: | ||
|
||
<CodeListing | ||
packageName="@ethdebug/pointers" | ||
sourcePath="test/solc.ts" | ||
extract={(sourceFile) => sourceFile.getFunction("singleSourceCompilation")} | ||
/> |
17 changes: 17 additions & 0 deletions
17
packages/web/docs/implementation-guides/pointers/testing/deployment.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
--- | ||
sidebar_position: 5 | ||
--- | ||
|
||
import CodeListing from "@site/src/components/CodeListing"; | ||
|
||
# Deploying contracts | ||
|
||
Deploying a contract with some EVM bytecode is straightforward with | ||
[EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) JavaScript provider objects, | ||
although it does require making a few RPC requests: | ||
|
||
<CodeListing | ||
packageName="@ethdebug/pointers" | ||
sourcePath="test/deploy.ts" | ||
extract={(sourceFile) => sourceFile.getFunction("deployContract")} | ||
/> |
33 changes: 33 additions & 0 deletions
33
packages/web/docs/implementation-guides/pointers/testing/example-pointers.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
--- | ||
sidebar_position: 2 | ||
--- | ||
|
||
import CodeListing from "@site/src/components/CodeListing"; | ||
|
||
# Finding example pointers | ||
|
||
These integration tests seek to minimize the use of bespoke data whose | ||
representations exist solely within the testing-associated code modules. | ||
|
||
Instead of containing custom pointer objects defined inline, the integration | ||
tests for this reference implementation use the official pointer examples | ||
that are distributed as part of the **ethdebug/format/pointer** schema itself. | ||
|
||
Since JSON Schema does not offer a means by which examples can be named (it | ||
only defines a way to represent an ordered list of unlabeled example values), | ||
these tests rely on searching for particular examples by their use of uniquely | ||
indicative string values (e.g., the "string storage" example pointer is the | ||
only example to contain the string `"string-storage-contract-variable-slot"`). | ||
|
||
The logic for doing this search is captured by the `findExamplePointer()` | ||
function: | ||
|
||
<CodeListing | ||
packageName="@ethdebug/pointers" | ||
sourcePath="test/examples.ts" | ||
extract={ | ||
sourceFile => sourceFile.getVariableStatement("findExamplePointer") | ||
} /> | ||
|
||
(This function is written as an immediately-invoked inline function so as to | ||
avoid unnecessary redundant calls to `describeSchema()`.) |
Oops, something went wrong.