Skip to content
Open
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
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ jobs:
path: testdata/openzeppelin-contracts/node_modules
key: ${{ runner.os }}-oz-node-modules-${{ hashFiles('testdata/openzeppelin-contracts/package-lock.json') }}

- name: Hardhat OZ compatibility suite (full)
run: make hardhat-tests-full
- name: Hardhat OZ compatibility suite
run: make hardhat-tests

- name: OZ baseline check (gate)
run: |
Expand Down
17 changes: 10 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -231,15 +231,18 @@ fetch-execution-specs-tests:
eth-tests: fetch-execution-specs-tests
@go test -test.fullpath=true -timeout 2000s -run ^TestEthereumTests$$ github.com/hyperledger/fabric-x-evm/integration

# Single-file smoke test — CI runs hardhat-tests-full (below) instead.
# Full OZ compatible set — what CI's oz-hardhat-compat job runs.
# Narrow it with FILE= and/or GREP= for a focused local run (no baseline diff):
# make hardhat-tests FILE=test/token/ERC20/ERC20.test.js
# make hardhat-tests GREP='ERC20 _mint'
# PORT= runs the testnode somewhere other than 8545, so a second run can go
# alongside one already in progress.
.PHONY: hardhat-tests
hardhat-tests:
@./scripts/run_hardhat_test.sh

# Full OZ compatible set — what CI's oz-hardhat-compat job runs.
.PHONY: hardhat-tests-full
hardhat-tests-full:
@./scripts/run_hardhat_test.sh --full
@./scripts/run_hardhat_test.sh \
$(if $(FILE),--file '$(FILE)') \
$(if $(GREP),--grep '$(GREP)') \
$(if $(PORT),--port '$(PORT)')

.PHONY: perf-tests
perf-tests: pre-pull-images
Expand Down
2 changes: 1 addition & 1 deletion cmd/baseline/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ instead of re-parsing the human-readable report. Same exit code either way.
`--results` is a glob (matched with `filepath.Glob`, so quote it against your shell expanding it
early) that merges several files — useful since a Mocha file that crashes at load time can zero out
the whole report for that invocation, so running per-file/per-directory and merging is safer than one
giant run. `testdata/oz-hardhat-results/` is exactly what `scripts/run_hardhat_test.sh --full` writes
giant run. `testdata/oz-hardhat-results/` is exactly what `scripts/run_hardhat_test.sh` writes
one file per top-level `test/` directory into, so `--suite oz-hardhat` alone works as-is after a real run.

Sample output, an empty baseline against a real run of `ERC20Permit.test.js` (regressions, since
Expand Down
146 changes: 111 additions & 35 deletions scripts/run_hardhat_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,56 @@ GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

FULL_SUITE=0
TEST_PATH="test/token/ERC20/ERC20.test.js"
if [ "${1:-}" = "--full" ]; then
FULL_SUITE=1
elif [ -n "${1:-}" ]; then
TEST_PATH="$1"
fi
DEFAULT_PORT=8545

usage() {
cat <<EOF
Usage: $(basename "$0") [--file <path>] [--grep <pattern>] [--port <n>]

With no arguments, runs the whole OpenZeppelin compatible set — one Hardhat
invocation per top-level test/ directory — and diffs the result against
testdata/oz_known_failures.json. This is what CI runs.

--file <path> Narrow to one test file (or directory) instead of the full set.
--grep <pattern> Narrow to tests whose full title matches <pattern>.
--port <n> Port for this run's testnode (default ${DEFAULT_PORT}). Use it to run
a second suite without disturbing one already going.
-h, --help Show this message.

A narrowed run skips the results/baseline step, since a partial run can't tell a
test that didn't fail from one that didn't run.

$(basename "$0") --file test/token/ERC20/ERC20.test.js
$(basename "$0") --file test/token/ERC20/ERC20.test.js --grep _approve
$(basename "$0") --grep 'ERC20 _mint'
$(basename "$0") --port 8600
EOF
}

TEST_FILE=""
GREP_PATTERN=""
PORT="${DEFAULT_PORT}"
while [ $# -gt 0 ]; do
case "$1" in
--file)
[ $# -ge 2 ] || { echo -e "${RED}Error: --file needs a path${NC}" >&2; exit 2; }
TEST_FILE="$2"; shift 2 ;;
--grep)
[ $# -ge 2 ] || { echo -e "${RED}Error: --grep needs a pattern${NC}" >&2; exit 2; }
GREP_PATTERN="$2"; shift 2 ;;
--port)
[ $# -ge 2 ] || { echo -e "${RED}Error: --port needs a number${NC}" >&2; exit 2; }
case "$2" in *[!0-9]*|'') echo -e "${RED}Error: --port must be a number, got '$2'${NC}" >&2; exit 2 ;; esac
PORT="$2"; shift 2 ;;
-h|--help)
usage; exit 0 ;;
*)
echo -e "${RED}Error: unknown argument '$1'${NC}" >&2
echo >&2
usage >&2
exit 2 ;;
esac
done
TESTNODE_PID=""

# The compatible set: every test/**/*.test.js except test/account/** and
Expand All @@ -35,7 +78,10 @@ RESULTS_DIR="${PROJECT_ROOT}/testdata/oz-hardhat-results"
cleanup() {
if [ -n "${TESTNODE_PID}" ] && kill -0 "${TESTNODE_PID}" 2>/dev/null; then
echo -e "\n${YELLOW}Stopping testnode (PID: ${TESTNODE_PID})${NC}"
kill "${TESTNODE_PID}" 2>/dev/null || true
# Negative PID kills the whole process group. `go run` execs the compiled
# program as a child, so killing only the parent leaves the server
# orphaned and still holding the port.
kill -- -"${TESTNODE_PID}" 2>/dev/null || true
wait "${TESTNODE_PID}" 2>/dev/null || true
fi
}
Expand Down Expand Up @@ -68,26 +114,32 @@ start_testnode() {
echo -e "${YELLOW}Starting self-contained fxevm testnode...${NC}"
cd "${PROJECT_ROOT}"

EXISTING_PID=$(lsof -ti :8545 || true)
# Refuse rather than kill: whatever is on the port may well be someone
# else's run, and taking it out from under them corrupts both.
EXISTING_PID=$(lsof -ti :"${PORT}" -sTCP:LISTEN 2>/dev/null || true)
if [ -n "${EXISTING_PID}" ]; then
echo "Killing existing process on port 8545 (PID: ${EXISTING_PID})"
kill "${EXISTING_PID}" 2>/dev/null || true
sleep 2
echo -e "${RED}Error: port ${PORT} is already in use (PID: ${EXISTING_PID})${NC}" >&2
echo "Stop it, or run this suite elsewhere with --port (make hardhat-tests PORT=8600)." >&2
exit 1
fi

echo "Starting testnode (logs: /tmp/testnode_$$.log)..."
go run ./cmd/fxevm testnode > "/tmp/testnode_$$.log" 2>&1 &
echo "Starting testnode on port ${PORT} (logs: /tmp/testnode_$$.log)..."
# Job control on, so the background job lands in its own process group and
# cleanup can take down go run and the server it execs together.
set -m
go run ./cmd/fxevm testnode --listen ":${PORT}" > "/tmp/testnode_$$.log" 2>&1 &
TESTNODE_PID=$!
set +m

echo "Waiting for testnode to be ready..."
MAX_RETRIES=30
RETRY_COUNT=0
while [ ${RETRY_COUNT} -lt ${MAX_RETRIES} ]; do
if curl -s -X POST -H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_accounts","params":[],"id":1}' \
http://127.0.0.1:8545 2>/dev/null | grep -q "result"; then
"http://127.0.0.1:${PORT}" 2>/dev/null | grep -q "result"; then
echo -e "${GREEN}Testnode is ready!${NC}"
export FABRIC_EVM_URL="http://127.0.0.1:8545"
export FABRIC_EVM_URL="http://127.0.0.1:${PORT}"
return 0
fi

Expand All @@ -109,18 +161,47 @@ start_testnode() {
exit 1
}

run_tests() {
echo -e "${YELLOW}Running Hardhat tests...${NC}"
echo "Test path: ${GREEN}${TEST_PATH}${NC}"
# compat_files echoes the compatible set for one top-level test/ directory:
# every *.test.js except the ones needing the hardhat-predeploy plugin.
compat_files() {
if [ "$1" = "utils" ]; then
find "test/$1" -name '*.test.js' ! -name 'Blockhash.test.js' | sort
else
find "test/$1" -name '*.test.js' | sort
fi
}

# run_narrowed handles --file/--grep: a single Hardhat invocation over whatever
# the caller asked for. No results file and no baseline diff — a partial run
# can't distinguish a test that didn't fail from one that never ran, so feeding
# it to `baseline check` would report the entire rest of the suite as stale.
run_narrowed() {
cd "${OZ_DIR}"
echo "Executing: npx hardhat test ${TEST_PATH} --config ${WRAPPER_CONFIG} --network fabricevm --bail"
npx hardhat test "${TEST_PATH}" --config "${WRAPPER_CONFIG}" --network fabricevm --bail

local files=()
if [ -n "${TEST_FILE}" ]; then
files=("${TEST_FILE}")
else
# --grep with no --file: search the whole compatible set.
for dir in "${COMPAT_DIRS[@]}"; do
while IFS= read -r f; do files+=("$f"); done < <(compat_files "${dir}")
done
fi

local args=(test "${files[@]}" --config "${WRAPPER_CONFIG}" --network fabricevm)
[ -n "${GREP_PATTERN}" ] && args+=(--grep "${GREP_PATTERN}")

echo -e "${YELLOW}Running Hardhat tests...${NC}"
[ -n "${TEST_FILE}" ] && echo -e "File: ${GREEN}${TEST_FILE}${NC}"
[ -n "${GREP_PATTERN}" ] && echo -e "Grep: ${GREEN}${GREP_PATTERN}${NC}"
# %q so a pattern with spaces stays copy-pasteable.
printf 'Executing: npx hardhat'; printf ' %q' "${args[@]}"; printf '\n'
npx hardhat "${args[@]}"
}

# run_full_suite drives the whole OZ compatible set, one Hardhat invocation per
# top-level test/ directory, with --bail off. Splitting per-directory (rather
# than one giant run) means a load-time crash in one directory's tests doesn't
# top-level test/ directory. Splitting per-directory (rather than one giant
# run) means a load-time crash in one directory's tests doesn't
# zero out the report for the other seven; each directory's output lands in
# its own file for `cmd/baseline` to glob-merge. HARDHAT_JSON_OUTPUT switches
# to the combined reporter, so the usual pass/fail console view still streams
Expand All @@ -134,11 +215,7 @@ run_full_suite() {
for dir in "${COMPAT_DIRS[@]}"; do
echo -e "${YELLOW}-- test/${dir} --${NC}"
local files=()
if [ "${dir}" = "utils" ]; then
while IFS= read -r f; do files+=("$f"); done < <(find "test/${dir}" -name '*.test.js' ! -name 'Blockhash.test.js' | sort)
else
while IFS= read -r f; do files+=("$f"); done < <(find "test/${dir}" -name '*.test.js' | sort)
fi
while IFS= read -r f; do files+=("$f"); done < <(compat_files "${dir}")

if HARDHAT_JSON_OUTPUT="${RESULTS_DIR}/${dir}.json" npx hardhat test "${files[@]}" \
--config "${WRAPPER_CONFIG}" --network fabricevm; then
Expand All @@ -154,10 +231,9 @@ run_full_suite() {

cd "${PROJECT_ROOT}"
# Report only — this script's own exit status stays tied to whether the
# suite ran at all, not to the baseline diff. Regressions failing the
# build is what the (not-yet-built) CI gate is for; a local, exploratory
# --full run shouldn't error out just because a known failure is still
# known to fail.
# suite ran at all, not to the baseline diff. Regressions failing the build
# is what CI's own gate step is for; a local run shouldn't error out just
# because a known failure is still known to fail.
go run ./cmd/baseline check --suite oz-hardhat || true
}

Expand All @@ -171,10 +247,10 @@ main() {
check_prerequisites
init_openzeppelin
start_testnode
if [ "${FULL_SUITE}" -eq 1 ]; then
run_full_suite
if [ -n "${TEST_FILE}" ] || [ -n "${GREP_PATTERN}" ]; then
run_narrowed
else
run_tests
run_full_suite
fi
}

Expand Down
Loading