Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9e2f0cf
errorcontains and replace tools
cce Dec 5, 2025
2fc846b
test: replace Error assertions with errorcontains.CaptureError
cce Dec 5, 2025
7c2aae0
test: use PID-specific output files to avoid contention
cce Dec 5, 2025
c63c969
errorcontains phase 3 tooling
cce Dec 5, 2025
fb2016a
test: update aggregate tool to use go-lcss library
cce Dec 6, 2025
e042315
test: convert CaptureError to ErrorContains/ErrorIs
cce Dec 6, 2025
778e453
test: convert more CaptureError calls to ErrorContains
cce Dec 8, 2025
1154ebe
test: convert remaining CaptureError calls to ErrorContains
cce Dec 8, 2025
89061cf
test: fix struct dump artifacts in ErrorContains assertions
cce Dec 8, 2025
e94f072
fix: correct error assertion bugs found during review
cce Dec 8, 2025
965e4fe
fix: replace struct dump artifact with overspend assertion
cce Dec 8, 2025
89bb480
fix: correct more error assertion issues from CI failures
cce Dec 8, 2025
3f5942b
fix: remove dynamic round/step values from error assertions
cce Dec 8, 2025
9de67a4
fix: address lint issues in errorcontains package
cce Dec 8, 2025
2e86fc1
switch to deadlock.Mutex
cce Dec 8, 2025
a620878
Merge remote-tracking branch 'upstream/master' into assert-errorcontains
cce Dec 8, 2025
e87b6b3
better / more specific error strings
cce Dec 10, 2025
895fc05
run modernize
cce Dec 10, 2025
31f5845
fix contains
cce Dec 11, 2025
f28f7e1
run make tidy
cce Dec 11, 2025
28e594f
add errorcontains output to artifacts
cce Dec 12, 2025
7df6e67
Merge remote-tracking branch 'upstream/master' into assert-errorcontains
cce Dec 12, 2025
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
22 changes: 21 additions & 1 deletion .github/workflows/ci-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,19 @@ jobs:
- name: Run tests
run: |
PACKAGES="$(go list ./... | grep -v /go-algorand/test/)"
export PACKAGE_NAMES=$(echo $PACKAGES | tr -d '\n')
export PACKAGE_NAMES=$(echo $PACKAGES | tr -d '\n')
mkdir -p test_results/${{ matrix.platform }}_test/${PARTITION_ID}
go tool -modfile=tool.mod gotestsum --format standard-quiet \
--junitfile ~/test_results/${{ matrix.platform }}_test/${PARTITION_ID}/results.xml \
--jsonfile ~/test_results/${{ matrix.platform }}_test/${PARTITION_ID}/testresults.json \
-- --tags "sqlite_unlock_notify sqlite_omit_load_extension" $SHORTTEST \
-race -timeout 1h -coverprofile=coverage.txt -covermode=atomic -p 4 \
$PACKAGE_NAMES
- name: Collect errorcontains capture files
if: ${{ !cancelled() }}
run: |
mkdir -p ~/test_results/${{ matrix.platform }}_test/${PARTITION_ID}/errorcontains
cp /tmp/error_capture.*.jsonl ~/test_results/${{ matrix.platform }}_test/${PARTITION_ID}/errorcontains/ 2>/dev/null || true
- name: Notify Slack on failure
if: failure()
uses: ./.github/actions/slack-notify
Expand Down Expand Up @@ -130,6 +135,11 @@ jobs:
mkdir -p ~/test_results/${{ matrix.platform }}_integration/${PARTITION_ID}
NO_BUILD=true TEST_RESULTS=~/test_results/${{ matrix.platform }}_integration/${PARTITION_ID} \
test/scripts/e2e.sh
- name: Collect errorcontains capture files
if: ${{ !cancelled() }}
run: |
mkdir -p ~/test_results/${{ matrix.platform }}_integration/${PARTITION_ID}/errorcontains
cp /tmp/error_capture.*.jsonl ~/test_results/${{ matrix.platform }}_integration/${PARTITION_ID}/errorcontains/ 2>/dev/null || true
- name: Notify Slack on failure
if: failure()
uses: ./.github/actions/slack-notify
Expand Down Expand Up @@ -186,6 +196,11 @@ jobs:
mkdir -p ~/test_results/${{ matrix.platform }}_e2e_expect/${PARTITION_ID}
NO_BUILD=true TEST_RESULTS=~/test_results/${{ matrix.platform }}_e2e_expect/${PARTITION_ID} \
test/scripts/e2e.sh
- name: Collect errorcontains capture files
if: ${{ !cancelled() }}
run: |
mkdir -p ~/test_results/${{ matrix.platform }}_e2e_expect/${PARTITION_ID}/errorcontains
cp /tmp/error_capture.*.jsonl ~/test_results/${{ matrix.platform }}_e2e_expect/${PARTITION_ID}/errorcontains/ 2>/dev/null || true
- name: Notify Slack on failure
if: failure()
uses: ./.github/actions/slack-notify
Expand Down Expand Up @@ -239,6 +254,11 @@ jobs:
mkdir -p ~/test_results/${{ matrix.platform }}_e2e_subs
NO_BUILD=true TEST_RESULTS=~/test_results/${{ matrix.platform }}_e2e_subs \
test/scripts/e2e.sh
- name: Collect errorcontains capture files
if: ${{ !cancelled() }}
run: |
mkdir -p ~/test_results/${{ matrix.platform }}_e2e_subs/errorcontains
cp /tmp/error_capture.*.jsonl ~/test_results/${{ matrix.platform }}_e2e_subs/errorcontains/ 2>/dev/null || true
- name: Notify Slack on failure
if: failure()
uses: ./.github/actions/slack-notify
Expand Down
16 changes: 8 additions & 8 deletions agreement/bundle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func TestBundleCreationWithZeroVotes(t *testing.T) {
makeBundlePanicWrapper(t, "makeBundle: no votes present in bundle (len(equivocationVotes) =", proposal, votes, nil)

bundle, err := ub.verify(context.Background(), ledger, avv)
require.Error(t, err)
require.ErrorContains(t, err, `unauthenticatedBundle.verify: b.Step = 0`)

bundles = append(bundles, bundle)
}
Expand Down Expand Up @@ -248,38 +248,38 @@ func TestBundleCreationWithEquivocationVotes(t *testing.T) {
voteBadCredBundle := unauthenticatedBundles[0]
voteBadCredBundle.Votes[0].Cred = committee.UnauthenticatedCredential{}
_, err := voteBadCredBundle.verify(context.Background(), ledger, avv)
require.Error(t, err)
require.ErrorContains(t, err, `sender was not selected`)

voteBadSenderBundle := unauthenticatedBundles[1]
voteBadSenderBundle.Votes[0].Sender = basics.Address{}
_, err = voteBadSenderBundle.verify(context.Background(), ledger, avv)
require.Error(t, err)
require.ErrorContains(t, err, `could not verify FS signature`)

voteNoQuorumBundle := unauthenticatedBundles[2]
voteNoQuorumBundle.Votes = voteNoQuorumBundle.Votes[:2]
voteNoQuorumBundle.EquivocationVotes = voteNoQuorumBundle.EquivocationVotes[:2]
_, err = voteNoQuorumBundle.verify(context.Background(), ledger, avv)
require.Error(t, err)
require.ErrorContains(t, err, `bundle: did not see enough votes: 1`)

evBadCredBundle := unauthenticatedBundles[3]
evBadCredBundle.EquivocationVotes[0].Cred = committee.UnauthenticatedCredential{}
_, err = evBadCredBundle.verify(context.Background(), ledger, avv)
require.Error(t, err)
require.ErrorContains(t, err, `sender was not selected`)

evBadEVBundle := unauthenticatedBundles[4]
evBadEVBundle.EquivocationVotes[0].Sigs = [2]crypto.OneTimeSignature{{}, {}}
_, err = evBadEVBundle.verify(context.Background(), ledger, avv)
require.Error(t, err)
require.ErrorContains(t, err, `could not verify FS signature`)

duplicateVoteBundle := unauthenticatedBundles[5]
duplicateVoteBundle.Votes = append(duplicateVoteBundle.Votes, duplicateVoteBundle.Votes[0])
_, err = duplicateVoteBundle.verify(context.Background(), ledger, avv)
require.Error(t, err)
require.ErrorContains(t, err, `was duplicated in bundle`)

duplicateEquivocationVoteBundle := unauthenticatedBundles[6]
duplicateEquivocationVoteBundle.EquivocationVotes = append(duplicateEquivocationVoteBundle.EquivocationVotes, duplicateEquivocationVoteBundle.EquivocationVotes[0])
_, err = duplicateEquivocationVoteBundle.verify(context.Background(), ledger, avv)
require.Error(t, err)
require.ErrorContains(t, err, `was duplicated in bundle`)

}

Expand Down
14 changes: 7 additions & 7 deletions agreement/certificate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ func TestCertificateBadCertificateWithFakeDoubleVote(t *testing.T) {

avv := MakeAsyncVoteVerifier(nil)
defer avv.Quit()
require.Error(t, cert.Authenticate(block, ledger, avv))
require.ErrorContains(t, cert.Authenticate(block, ledger, avv), `not an equivocation pair`)
}

func TestCertificateDifferentBlock(t *testing.T) {
Expand All @@ -204,12 +204,12 @@ func TestCertificateDifferentBlock(t *testing.T) {
bundle := unauthenticatedBundle(cert)
require.NotEqual(t, Certificate{}, cert)

require.Error(t, cert.claimsToAuthenticate(block))
require.ErrorContains(t, cert.claimsToAuthenticate(block), `certificate claims to validate the wrong hash`)

avv := MakeAsyncVoteVerifier(nil)
defer avv.Quit()
require.NoError(t, verifyBundleAgainstLedger(bundle, ledger, avv))
require.Error(t, cert.Authenticate(block, ledger, avv))
require.ErrorContains(t, cert.Authenticate(block, ledger, avv), `certificate claims to validate the wrong hash`)
}

func TestCertificateNoCertStep(t *testing.T) {
Expand Down Expand Up @@ -293,12 +293,12 @@ func TestCertificateCertWrongRound(t *testing.T) {
cert := makeCertTesting(block.Digest(), votes, equiVotes)
bundle := unauthenticatedBundle(cert)
require.NotEqual(t, Certificate{}, cert)
require.Error(t, cert.claimsToAuthenticate(block))
require.ErrorContains(t, cert.claimsToAuthenticate(block), `certificate claims to validate the wrong round: 1 != 0`)

avv := MakeAsyncVoteVerifier(nil)
defer avv.Quit()
require.NoError(t, verifyBundleAgainstLedger(bundle, ledger, avv))
require.Error(t, cert.Authenticate(block, ledger, avv))
require.ErrorContains(t, cert.Authenticate(block, ledger, avv), `certificate claims to validate the wrong round: 1 != 0`)
}

func TestCertificateCertWithTooFewVotes(t *testing.T) {
Expand Down Expand Up @@ -363,6 +363,6 @@ func TestCertificateDupVote(t *testing.T) {

avv := MakeAsyncVoteVerifier(nil)
defer avv.Quit()
require.Error(t, verifyBundleAgainstLedger(bundle, ledger, avv))
require.Error(t, cert.Authenticate(block, ledger, avv))
require.ErrorContains(t, verifyBundleAgainstLedger(bundle, ledger, avv), `was duplicated in bundle`)
require.ErrorContains(t, cert.Authenticate(block, ledger, avv), `was duplicated in bundle`)
}
1 change: 1 addition & 0 deletions agreement/fuzzer/dropMessageFilter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package fuzzer

import (
"encoding/json"

"github.com/algorand/go-algorand/protocol"
)

Expand Down
1 change: 1 addition & 0 deletions agreement/fuzzer/messageDelayFilter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package fuzzer
import (
"container/heap"
"encoding/json"

"github.com/algorand/go-deadlock"

"github.com/algorand/go-algorand/protocol"
Expand Down
1 change: 1 addition & 0 deletions agreement/fuzzer/messagePriorityQueue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package fuzzer

import (
"container/heap"

"github.com/algorand/go-algorand/protocol"
)

Expand Down
4 changes: 2 additions & 2 deletions agreement/proposalTracker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func TestProposalTrackerProposalSeeker(t *testing.T) {
assert.Equal(t, s.Lowest, s.lowestIncludingLate)

s, effect, err = s.accept(votes[3])
assert.Error(t, err)
assert.ErrorContains(t, err, `proposalSeeker.accept: credential from`)
assert.Equal(t, effect, NoLateCredentialTrackingImpact)
assert.False(t, s.Frozen)
assert.True(t, s.Filled)
Expand All @@ -103,7 +103,7 @@ func TestProposalTrackerProposalSeeker(t *testing.T) {
assert.Equal(t, s.Lowest, s.lowestIncludingLate)

s, effect, err = s.accept(votes[0])
assert.Error(t, err)
assert.ErrorContains(t, err, `proposalSeeker.accept: seeker is already frozen`)
assert.Equal(t, effect, VerifiedBetterLateCredentialForTracking)
assert.Equal(t, s.Lowest, lowestBeforeFreeze)
assert.True(t, s.Frozen)
Expand Down
8 changes: 4 additions & 4 deletions agreement/proposal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,10 @@ func TestProposalFunctions(t *testing.T) {
require.NoError(t, err)

err = proposalValue.matches(encDigest, encDigest)
require.Error(t, err)
require.ErrorContains(t, err, `proposal block digest mismatches payload`)

err = proposalValue.matches(digest, digest)
require.Error(t, err)
require.ErrorContains(t, err, `proposal encoding digest mismatches payload`)

}
}
Expand Down Expand Up @@ -182,7 +182,7 @@ func TestProposalUnauthenticated(t *testing.T) {

// test bad round number
proposal, err = unauthenticatedProposal.validate(context.Background(), round+1, ledger, validator)
require.Error(t, err)
require.ErrorContains(t, err, `proposed entry from wrong round: entry.Round() != current: 1 != 2`)
proposal, err = unauthenticatedProposal.validate(context.Background(), round, ledger, validator)
require.NotNil(t, proposal)
require.NoError(t, err)
Expand All @@ -200,7 +200,7 @@ func TestProposalUnauthenticated(t *testing.T) {
unauthenticatedProposal3 := proposal3.u()
unauthenticatedProposal3.SeedProof = unauthenticatedProposal.SeedProof
_, err = unauthenticatedProposal3.validate(context.Background(), round, ledger, validator)
require.Error(t, err)
require.ErrorContains(t, err, `unable to verify header: seed proof malformed (`)

// validate mismatch proposer address between block and unauthenticatedProposal
proposal4, _, _ := proposalForBlock(accounts.addresses[accountIndex], accounts.vrfs[accountIndex], testBlockFactory, period, ledger)
Expand Down
7 changes: 3 additions & 4 deletions agreement/pseudonode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ func TestPseudonode(t *testing.T) {
}
channels = append(channels, ch)
}
assert.Error(t, err, "MakeProposals did not returned an error when being overflowed with requests")
assert.ErrorContains(t, err, `pseudonode input channel is full`, "MakeProposals did not returned an error when being overflowed with requests")

persist := make(chan error)
close(persist)
Expand All @@ -186,7 +186,7 @@ func TestPseudonode(t *testing.T) {
}
channels = append(channels, ch)
}
assert.Error(t, err, "MakeVotes did not returned an error when being overflowed with requests")
assert.ErrorContains(t, err, `pseudonode input channel is full`, "MakeVotes did not returned an error when being overflowed with requests")

// drain output channels.
for _, ch := range channels {
Expand Down Expand Up @@ -525,7 +525,6 @@ func TestPseudonodeNonEnqueuedTasks(t *testing.T) {
channels = append(channels, ch)
}
enqueuedProposals := len(channels)
require.Error(t, err, "MakeProposals did not returned an error when being overflowed with requests")
require.ErrorIs(t, err, errPseudonodeBacklogFull)

persist := make(chan error)
Expand All @@ -538,7 +537,7 @@ func TestPseudonodeNonEnqueuedTasks(t *testing.T) {
}
channels = append(channels, ch)
}
require.Error(t, err, "MakeVotes did not returned an error when being overflowed with requests")
require.ErrorContains(t, err, `pseudonode input channel is full`, "MakeVotes did not returned an error when being overflowed with requests")
enqueuedVotes := len(channels) - enqueuedProposals
// drain output channels.
for _, ch := range channels {
Expand Down
38 changes: 19 additions & 19 deletions agreement/vote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,37 +113,37 @@ func TestVoteValidation(t *testing.T) {
noSig := unauthenticatedVote
noSig.Sig = crypto.OneTimeSignature{}
_, err = noSig.verify(ledger)
require.Error(t, err)
require.ErrorContains(t, err, `could not verify FS signature`)

noCred := unauthenticatedVote
noCred.Cred = committee.UnauthenticatedCredential{}
_, err = noCred.verify(ledger)
require.Error(t, err)
require.ErrorContains(t, err, `sender was not selected`)

badRound := unauthenticatedVote
badRound.R.Round++
_, err = badRound.verify(ledger)
require.Error(t, err)
require.ErrorContains(t, err, `could not verify FS signature`)

badPeriod := unauthenticatedVote
badPeriod.R.Period++
_, err = badPeriod.verify(ledger)
require.Error(t, err)
require.ErrorContains(t, err, `could not verify FS signature`)

badStep := unauthenticatedVote
badStep.R.Step++
_, err = badStep.verify(ledger)
require.Error(t, err)
require.ErrorContains(t, err, `could not verify FS signature`)

badBlockHash := unauthenticatedVote
badBlockHash.R.Proposal.BlockDigest = randomBlockHash()
_, err = badBlockHash.verify(ledger)
require.Error(t, err)
require.ErrorContains(t, err, `could not verify FS signature`)

badProposer := unauthenticatedVote
badProposer.R.Proposal.OriginalProposer = basics.Address(randomBlockHash())
_, err = badProposer.verify(ledger)
require.Error(t, err)
require.ErrorContains(t, err, `could not verify FS signature`)
}
}
require.True(t, processedVote, "No votes were processed")
Expand Down Expand Up @@ -194,7 +194,7 @@ func TestVoteReproposalValidation(t *testing.T) {
badReproposalVote, err := makeVote(rv, otSecrets[i], vrfSecrets[i], ledger)
require.NoError(t, err)
_, err = badReproposalVote.verify(ledger)
require.Error(t, err)
require.ErrorContains(t, err, `unauthenticatedVote.verify: proposal-vote sender mismatches with proposal-value`)

// bad period-1 reproposal for a period 2 original proposal
rv = rawVote{Sender: address, Round: round, Period: per, Step: step(0), Proposal: proposal}
Expand All @@ -203,7 +203,7 @@ func TestVoteReproposalValidation(t *testing.T) {
badReproposalVote, err = makeVote(rv, otSecrets[i], vrfSecrets[i], ledger)
require.NoError(t, err)
_, err = badReproposalVote.verify(ledger)
require.Error(t, err)
require.ErrorContains(t, err, `unauthenticatedVote.verify: proposal-vote in period 1 claims to repropose block from future period 2`)
}
}
require.True(t, processedVote, "No votes were processed")
Expand Down Expand Up @@ -274,7 +274,7 @@ func TestVoteValidationStepCertAndProposalBottom(t *testing.T) {
unauthenticatedVote.R.Step = cert
unauthenticatedVote.R.Proposal = bottom
_, err = unauthenticatedVote.verify(ledger)
require.Error(t, err)
require.ErrorContains(t, err, `unauthenticatedVote.verify: votes from step 2 cannot validate bottom`)

}
}
Expand Down Expand Up @@ -343,7 +343,7 @@ func TestEquivocationVoteValidation(t *testing.T) {

// check for same vote
_, err = evSameVote.verify(ledger)
require.Error(t, err)
require.ErrorContains(t, err, `isEquivocationPair: not an equivocation pair: identical vote (block hash`)

// test vote accessors
v0 := aev.v0()
Expand All @@ -357,42 +357,42 @@ func TestEquivocationVoteValidation(t *testing.T) {
noSig := ev
noSig.Sigs = [2]crypto.OneTimeSignature{{}, {}}
_, err = noSig.verify(ledger)
require.Error(t, err)
require.ErrorContains(t, err, `could not verify FS signature`)

noCred := ev
noCred.Cred = committee.UnauthenticatedCredential{}
_, err = noCred.verify(ledger)
require.Error(t, err)
require.ErrorContains(t, err, `sender was not selected`)

badRound := ev
badRound.Round++
_, err = badRound.verify(ledger)
require.Error(t, err)
require.ErrorContains(t, err, `could not verify FS signature`)

badPeriod := ev
badPeriod.Period++
_, err = badPeriod.verify(ledger)
require.Error(t, err)
require.ErrorContains(t, err, `could not verify FS signature`)

badStep := ev
badStep.Step++
_, err = badStep.verify(ledger)
require.Error(t, err)
require.ErrorContains(t, err, `could not verify FS signature`)

badBlockHash1 := ev
badBlockHash1.Proposals[0].BlockDigest = randomBlockHash()
_, err = badBlockHash1.verify(ledger)
require.Error(t, err)
require.ErrorContains(t, err, `could not verify FS signature`)

badBlockHash2 := ev
badBlockHash2.Proposals[1].BlockDigest = randomBlockHash()
_, err = badBlockHash2.verify(ledger)
require.Error(t, err)
require.ErrorContains(t, err, `could not verify FS signature`)

badSender := ev
badSender.Sender = basics.Address{}
_, err = badSender.verify(ledger)
require.Error(t, err)
require.ErrorContains(t, err, `unauthenticatedEquivocationVote.verify: failed to verify pair 0: unauthenticatedVote.verify: could not verify FS signature on vote by AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ given`)
}
}
require.True(t, processedVote, "No votes were processed")
Expand Down
Loading
Loading