Merge pull request #2553 from ethereum/extract-docs-tests

Extract examples from documentation and run tests on it
This commit is contained in:
chriseth 2017-07-13 22:16:49 +02:00 committed by GitHub
commit 63bf0f68e6
10 changed files with 176 additions and 42 deletions

View File

@ -182,6 +182,8 @@ Given the contract:
::
pragma solidity ^0.4.0;
contract Foo {
function bar(bytes3[2] xy) {}
function baz(uint32 x, bool y) returns (bool r) { r = x > 32 || y; }

View File

@ -679,6 +679,8 @@ Example:
We will follow an example compilation from Solidity to desugared assembly.
We consider the runtime bytecode of the following Solidity program::
pragma solidity ^0.4.0;
contract C {
function f(uint x) returns (uint y) {
y = 1;

View File

@ -213,6 +213,8 @@ In the following example, ``D``, can call ``c.getData()`` to retrieve the value
::
// This will not compile
pragma solidity ^0.4.0;
contract C {
@ -545,9 +547,11 @@ Please ensure you test your fallback function thoroughly to ensure the execution
test.call(0xabcdef01); // hash does not exist
// results in test.x becoming == 1.
// The following call will fail, reject the
// Ether and return false:
test.send(2 ether);
// The following will not compile, but even
// if someone sends ether to that contract,
// the transaction will fail and reject the
// Ether.
//test.send(2 ether);
}
}
@ -773,13 +777,17 @@ seen in the following example::
pragma solidity ^0.4.0;
contract owned {
function owned() { owner = msg.sender; }
address owner;
}
contract mortal is owned {
function kill() {
if (msg.sender == owner) selfdestruct(owner);
}
}
contract Base1 is mortal {
function kill() { /* do cleanup 1 */ mortal.kill(); }
}
@ -800,6 +808,11 @@ derived override, but this function will bypass
pragma solidity ^0.4.0;
contract owned {
function owned() { owner = msg.sender; }
address owner;
}
contract mortal is owned {
function kill() {
if (msg.sender == owner) selfdestruct(owner);
@ -879,6 +892,8 @@ error "Linearization of inheritance graph impossible".
::
// This will not compile
pragma solidity ^0.4.0;
contract X {}
@ -914,10 +929,16 @@ Contract functions can lack an implementation as in the following example (note
function utterance() returns (bytes32);
}
Such contracts cannot be compiled (even if they contain implemented functions alongside non-implemented functions), but they can be used as base contracts::
Such contracts cannot be compiled (even if they contain
implemented functions alongside non-implemented functions),
but they can be used as base contracts::
pragma solidity ^0.4.0;
contract Feline {
function utterance() returns (bytes32);
}
contract Cat is Feline {
function utterance() returns (bytes32) { return "miaow"; }
}

View File

@ -20,6 +20,8 @@ For example, suppose we want our contract to
accept one kind of external calls with two integers, we would write
something like::
pragma solidity ^0.4.0;
contract Simple {
function taker(uint _a, uint _b) {
// do something with _a and _b.
@ -34,6 +36,8 @@ The output parameters can be declared with the same syntax after the
the sum and the product of the two given integers, then we would
write::
pragma solidity ^0.4.0;
contract Simple {
function arithmetics(uint _a, uint _b) returns (uint o_sum, uint o_product) {
o_sum = _a + _b;
@ -91,6 +95,8 @@ Internal Function Calls
Functions of the current contract can be called directly ("internally"), also recursively, as seen in
this nonsensical example::
pragma solidity ^0.4.0;
contract C {
function g(uint a) returns (uint ret) { return f(); }
function f() returns (uint ret) { return g(7) + f(); }
@ -116,6 +122,8 @@ all function arguments have to be copied to memory.
When calling functions of other contracts, the amount of Wei sent with the call and
the gas can be specified with special options ``.value()`` and ``.gas()``, respectively::
pragma solidity ^0.4.0;
contract InfoFeed {
function info() payable returns (uint ret) { return 42; }
}
@ -173,7 +181,9 @@ parameters from the function declaration, but can be in arbitrary order.
pragma solidity ^0.4.0;
contract C {
function f(uint key, uint value) { ... }
function f(uint key, uint value) {
// ...
}
function g() {
// named arguments
@ -261,6 +271,8 @@ Destructuring Assignments and Returning Multiple Values
Solidity internally allows tuple types, i.e. a list of objects of potentially different types whose size is a constant at compile-time. Those tuples can be used to return multiple values at the same time and also assign them to multiple variables (or LValues in general) at the same time::
pragma solidity ^0.4.0;
contract C {
uint[] data;
@ -313,6 +325,8 @@ This happens because Solidity inherits its scoping rules from JavaScript.
This is in contrast to many languages where variables are only scoped where they are declared until the end of the semantic block.
As a result, the following code is illegal and cause the compiler to throw an error, ``Identifier already declared``::
// This will not compile
pragma solidity ^0.4.0;
contract ScopingErrors {

View File

@ -48,6 +48,8 @@ non-elementary type, the positions are found by adding an offset of ``keccak256(
So for the following contract snippet::
pragma solidity ^0.4.0;
contract C {
struct s { uint a; uint b; }
uint x;

View File

@ -179,11 +179,13 @@ Never use tx.origin for authorization. Let's say you have a wallet contract like
}
}
Now someone tricks you into sending ether to the address of this attack wallet:
Now someone tricks you into sending ether to the address of this attack wallet::
::
pragma solidity ^0.4.11;
pragma solidity ^0.4.0;
interface TxUserWallet {
function transferTo(address dest, uint amount);
}
contract TxAttackWallet {
address owner;

View File

@ -376,7 +376,7 @@ Example that shows how to use internal function types::
function (uint, uint) returns (uint) f
)
internal
returns (uint)
returns (uint r)
{
r = self[0];
for (uint i = 1; i < self.length; i++) {
@ -599,6 +599,8 @@ possible:
::
// This will not compile.
pragma solidity ^0.4.0;
contract C {
@ -606,6 +608,7 @@ possible:
// The next line creates a type error because uint[3] memory
// cannot be converted to uint[] memory.
uint[] x = [uint(1), 3, 4];
}
}
It is planned to remove this restriction in the future but currently creates
@ -812,8 +815,9 @@ for each ``_KeyType``, recursively.
}
contract MappingUser {
address contractAddress = 0x42;
function f() returns (uint) {
return MappingExample(<address>).balances(this);
return MappingExample(contractAddress).balances(this);
}
}

View File

@ -1,6 +1,6 @@
#!/usr/bin/python
#
# This script reads C++ source files and writes all
# This script reads C++ or RST source files and writes all
# multi-line strings into individual files.
# This can be used to extract the Solidity test cases
# into files for e.g. fuzz testing as
@ -12,7 +12,7 @@ import os
import hashlib
from os.path import join
def extract_cases(path):
def extract_test_cases(path):
lines = open(path, 'rb').read().splitlines()
inside = False
@ -34,6 +34,44 @@ def extract_cases(path):
return tests
# Contract sources are indented by 4 spaces.
# Look for `pragma solidity` and abort a line not indented properly.
# If the comment `// This will not compile` is above the pragma,
# the code is skipped.
def extract_docs_cases(path):
# Note: this code works, because splitlines() removes empty new lines
# and thus even if the empty new lines are missing indentation
lines = open(path, 'rb').read().splitlines()
ignore = False
inside = False
tests = []
for l in lines:
if inside:
# Abort if indentation is missing
m = re.search(r'^[^ ]+', l)
if m:
inside = False
else:
tests[-1] += l + '\n'
else:
m = re.search(r'^ // This will not compile', l)
if m:
ignore = True
if ignore:
# Abort if indentation is missing
m = re.search(r'^[^ ]+', l)
if m:
ignore = False
else:
m = re.search(r'^ pragma solidity .*[0-9]+\.[0-9]+\.[0-9]+;$', l)
if m:
inside = True
tests += [l]
return tests
def write_cases(tests):
for test in tests:
@ -41,8 +79,17 @@ def write_cases(tests):
if __name__ == '__main__':
path = sys.argv[1]
docs = False
if len(sys.argv) > 2 and sys.argv[2] == 'docs':
docs = True
for root, dir, files in os.walk(path):
for root, subdirs, files in os.walk(path):
if '_build' in subdirs:
subdirs.remove('_build')
for f in files:
cases = extract_cases(join(root, f))
path = join(root, f)
if docs:
cases = extract_docs_cases(path)
else:
cases = extract_test_cases(path)
write_cases(cases)

View File

@ -30,27 +30,6 @@ set -e
REPO_ROOT="$(dirname "$0")"/..
echo "Checking that StandardToken.sol, owned.sol and mortal.sol produce bytecode..."
output=$("$REPO_ROOT"/build/solc/solc --bin "$REPO_ROOT"/std/*.sol 2>/dev/null | grep "ffff" | wc -l)
test "${output//[[:blank:]]/}" = "3"
echo "Compiling various other contracts and libraries..."
(
cd "$REPO_ROOT"/test/compilationTests/
for dir in *
do
if [ "$dir" != "README.md" ]
then
echo " - $dir"
cd "$dir"
../../../build/solc/solc --optimize \
--combined-json abi,asm,ast,bin,bin-runtime,clone-bin,compact-format,devdoc,hashes,interface,metadata,opcodes,srcmap,srcmap-runtime,userdoc \
*.sol */*.sol > /dev/null 2>&1
cd ..
fi
done
)
echo "Running commandline tests..."
"$REPO_ROOT/test/cmdlineTests.sh"

View File

@ -28,26 +28,86 @@
set -e
REPO_ROOT="$(dirname "$0")"/..
REPO_ROOT=$(cd $(dirname "$0")/.. && pwd)
echo $REPO_ROOT
SOLC="$REPO_ROOT/build/solc/solc"
echo "Checking that the bug list is up to date..."
"$REPO_ROOT"/scripts/update_bugs_by_version.py
echo "Compiling all files in std and examples..."
echo "Checking that StandardToken.sol, owned.sol and mortal.sol produce bytecode..."
output=$("$REPO_ROOT"/build/solc/solc --bin "$REPO_ROOT"/std/*.sol 2>/dev/null | grep "ffff" | wc -l)
test "${output//[[:blank:]]/}" = "3"
for f in "$REPO_ROOT"/std/*.sol
do
echo "Compiling $f..."
function compileFull()
{
files="$*"
set +e
output=$("$SOLC" "$f" 2>&1)
"$SOLC" --optimize \
--combined-json abi,asm,ast,bin,bin-runtime,clone-bin,compact-format,devdoc,hashes,interface,metadata,opcodes,srcmap,srcmap-runtime,userdoc \
$files >/dev/null 2>&1
failed=$?
set -e
if [ $failed -ne 0 ]
then
echo "Compilation failed on:"
cat $files
false
fi
}
function compileWithoutWarning()
{
files="$*"
set +e
output=$("$SOLC" $files 2>&1)
failed=$?
# Remove the pre-release warning from the compiler output
output=$(echo "$output" | grep -v 'pre-release')
echo "$output"
set -e
test -z "$output" -a "$failed" -eq 0
}
echo "Compiling various other contracts and libraries..."
(
cd "$REPO_ROOT"/test/compilationTests/
for dir in *
do
if [ "$dir" != "README.md" ]
then
echo " - $dir"
cd "$dir"
compileFull *.sol */*.sol
cd ..
fi
done
)
echo "Compiling all files in std and examples..."
for f in "$REPO_ROOT"/std/*.sol
do
echo "$f"
compileWithoutWarning "$f"
done
echo "Compiling all examples from the documentation..."
TMPDIR=$(mktemp -d)
(
set -e
cd "$REPO_ROOT"
REPO_ROOT=$(pwd) # make it absolute
cd "$TMPDIR"
"$REPO_ROOT"/scripts/isolate_tests.py "$REPO_ROOT"/docs/ docs
for f in *.sol
do
echo "$f"
compileFull "$TMPDIR/$f"
done
)
rm -rf "$TMPDIR"
echo "Done."
echo "Testing library checksum..."
echo '' | "$SOLC" --link --libraries a:0x90f20564390eAe531E810af625A22f51385Cd222
@ -77,6 +137,7 @@ TMPDIR=$(mktemp -d)
REPO_ROOT=$(pwd) # make it absolute
cd "$TMPDIR"
"$REPO_ROOT"/scripts/isolate_tests.py "$REPO_ROOT"/test/
"$REPO_ROOT"/scripts/isolate_tests.py "$REPO_ROOT"/docs/ docs
for f in *.sol
do
set +e