Skip to content

Commit

Permalink
feat: multi massif test for node command (#32)
Browse files Browse the repository at this point in the history
* feat: multi massif test for node command

* fix: linter issue

* fix: restore stdout after each test even if its not been read

---------

Co-authored-by: Robin Bryce <[email protected]>
  • Loading branch information
robinbryce and Robin Bryce authored Sep 17, 2024
1 parent 91e0dfc commit e5a03de
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 11 deletions.
62 changes: 52 additions & 10 deletions tests/integrationsuite.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package tests

import (
"bytes"
"io"
"os"

"github.com/datatrails/go-datatrails-common/logger"
Expand Down Expand Up @@ -35,10 +37,13 @@ const (
type IntegrationTestSuite struct {
suite.Suite

Env TestEnv
origStdin *os.File
stdinWriter *os.File
stdinReader *os.File
Env TestEnv
origStdin *os.File
stdinWriter *os.File
stdinReader *os.File
origStdout *os.File
stdoutWriter *os.File
stdoutReader *os.File
}

// StdinWriteAndClose writes the provided bytes to std in and closes the write
Expand All @@ -59,6 +64,7 @@ func (s *IntegrationTestSuite) StdinWriteAndClose(b []byte) (int, error) {
func (s *IntegrationTestSuite) SetupSuite() {
// capture this as early as possible
s.origStdin = os.Stdin
s.origStdout = os.Stdout
}

// EnsureAzuriteEnv ensures the environment variables for azurite are set
Expand All @@ -81,18 +87,38 @@ func (s *IntegrationTestSuite) EnsureAzuriteEnv() {
}
}

func (s *IntegrationTestSuite) ReplaceStdIO() {
func (s *IntegrationTestSuite) ReplaceStdin() {
var err error
require := s.Require()
require.NotNil(s.origStdin)
s.restoreStdIO()
s.restoreStdin()
s.stdinReader, s.stdinWriter, err = os.Pipe()
require.NoError(err)
os.Stdin = s.stdinReader
// Note, we don't mess with stdout
}

func (s *IntegrationTestSuite) restoreStdIO() {
func (s *IntegrationTestSuite) ReplaceStdout() {
var err error
require := s.Require()
require.NotNil(s.origStdout)
s.restoreStdout()
s.stdoutReader, s.stdoutWriter, err = os.Pipe()
require.NoError(err)
os.Stdout = s.stdoutWriter
}

func (s *IntegrationTestSuite) CaptureAndCloseStdout() string {
s.Require().NotNil(s.stdoutReader)
s.stdoutWriter.Close()
s.stdoutWriter = nil
var buf bytes.Buffer
_, err := io.Copy(&buf, s.stdoutReader)
s.Require().NoError(err)
s.restoreStdout()
return buf.String()
}

func (s *IntegrationTestSuite) restoreStdin() {
os.Stdin = s.origStdin

if s.stdinWriter != nil {
Expand All @@ -105,6 +131,19 @@ func (s *IntegrationTestSuite) restoreStdIO() {
s.stdinReader = nil
}

func (s *IntegrationTestSuite) restoreStdout() {
os.Stdout = s.origStdout

if s.stdoutWriter != nil {
s.stdoutWriter.Close()
}
if s.stdoutReader != nil {
s.stdoutReader.Close()
}
s.stdoutWriter = nil
s.stdoutReader = nil
}

// BeforeTest is run before the test
//
// It gets the correct suite wide test environment
Expand All @@ -113,7 +152,9 @@ func (s *IntegrationTestSuite) BeforeTest(suiteName, testName string) {

var err error
require := s.Require()
s.ReplaceStdIO()
s.ReplaceStdin()

// Note: do NOT replace stdout by default

logger.New("NOOP")
defer logger.OnExit()
Expand All @@ -128,5 +169,6 @@ func (s *IntegrationTestSuite) BeforeTest(suiteName, testName string) {
// Currently used to print useful information for failing tests
func (s *IntegrationTestSuite) AfterTest(suiteName, testName string) {

s.restoreStdIO()
s.restoreStdin()
s.restoreStdout()
}
121 changes: 121 additions & 0 deletions tests/node/node_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
//go:build integration && azurite

package node

import (
"fmt"
"strings"

"github.com/datatrails/go-datatrails-common/logger"
"github.com/datatrails/go-datatrails-logverification/integrationsupport"
"github.com/datatrails/go-datatrails-merklelog/massifs"
"github.com/datatrails/go-datatrails-merklelog/mmr"
"github.com/datatrails/go-datatrails-merklelog/mmrtesting"
"github.com/datatrails/go-datatrails-simplehash/simplehash"
"github.com/datatrails/veracity"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func (s *NodeSuite) newMMRTestingConfig(labelPrefix, tenantIdentity string) mmrtesting.TestConfig {
return mmrtesting.TestConfig{
StartTimeMS: (1698342521) * 1000, EventRate: 500,
TestLabelPrefix: labelPrefix,
TenantIdentity: tenantIdentity,
Container: strings.ReplaceAll(strings.ToLower(labelPrefix), "_", ""),
}
}

// TestNodeMultiMassif tests that the veracity sub command node
// works for massifs beyond the first one and covers some obvious edge cases.
// This really just tests that the correspondence between the massif index and the leaf index holds
// regardless of the massif count.
func (s *NodeSuite) TestVerifyIncludedMultiMassif() {
logger.New("TestNodeMultiMassif")
defer logger.OnExit()

cfg := s.newMMRTestingConfig("TestNodeMultiMassif", "")
azurite := mmrtesting.NewTestContext(s.T(), cfg)

massifHeight := uint8(8)
leavesPerMassif := mmr.HeightIndexLeafCount(uint64(massifHeight) - 1)

tests := []struct {
name string
massifCount uint32
// leaf indices to check.
leaves []uint64
}{
// make sure we cover the obvious edge cases
{name: "leaf 0", massifCount: 1, leaves: []uint64{0}},
{name: "single massif first few and last few", massifCount: 1, leaves: []uint64{0, 1, 2, leavesPerMassif - 2, leavesPerMassif - 1}},
{name: "2 massifs, last of first and first of last", massifCount: 2, leaves: []uint64{leavesPerMassif - 1, leavesPerMassif}},
{name: "5 massifs, first and last of each", massifCount: 5, leaves: []uint64{
0, leavesPerMassif - 1,
1 * leavesPerMassif, 2*leavesPerMassif - 1,
2 * leavesPerMassif, 3*leavesPerMassif - 1,
3 * leavesPerMassif, 4*leavesPerMassif - 1,
}},
}

// note: VERACITY_IKWID is set in main, we need it to enable --envauth so we force it here
app := veracity.NewApp("tests", true)
veracity.AddCommands(app, true)

for _, tt := range tests {

massifCount := tt.massifCount
s.Run(fmt.Sprintf("massifCount:%d", massifCount), func() {

leafHasher := integrationsupport.NewLeafHasher()
g := integrationsupport.NewTestGenerator(
s.T(), cfg.StartTimeMS/1000, &leafHasher, mmrtesting.TestGeneratorConfig{
StartTimeMS: cfg.StartTimeMS,
EventRate: cfg.EventRate,
TenantIdentity: cfg.TenantIdentity,
TestLabelPrefix: cfg.TestLabelPrefix,
})

tenantId0 := g.NewTenantIdentity()
events := integrationsupport.GenerateTenantLog(
&azurite, g, int(tt.massifCount)*int(leavesPerMassif), tenantId0, true,
massifHeight,
)

for _, iLeaf := range tt.leaves {

s.ReplaceStdout()

mmrIndex := mmr.TreeIndex(iLeaf)

err := app.Run([]string{
"veracity",
"--envauth", // uses the emulator
"--container", cfg.Container,
"--data-url", s.Env.AzuriteVerifiableDataURL,
"--tenant", tenantId0,
"--height", fmt.Sprintf("%d", massifHeight),
"node",
"--mmrindex", fmt.Sprintf("%d", mmrIndex),
})
s.NoError(err)

stdout := s.CaptureAndCloseStdout()

id, _, err := massifs.SplitIDTimestampHex(events[iLeaf].MerklelogEntry.Commit.Idtimestamp)
require.NoError(s.T(), err)

hasher := simplehash.NewHasherV3()
// hash the generated event
err = hasher.HashEvent(
events[iLeaf],
simplehash.WithPrefix([]byte{byte(integrationsupport.LeafTypePlain)}),
simplehash.WithIDCommitted(id),
)
require.Nil(s.T(), err)
leafValue := fmt.Sprintf("%x", hasher.Sum(nil))
assert.Equal(s.T(), leafValue, strings.TrimSpace(stdout))
}
})
}
}
20 changes: 20 additions & 0 deletions tests/node/suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//go:build integration && azurite

package node

import (
"testing"

"github.com/datatrails/veracity/tests"
"github.com/stretchr/testify/suite"
)

// NodeSuite deals with veracity based verification that DataTrails events are included in a Merkle Log
type NodeSuite struct {
tests.IntegrationTestSuite
}

func TestNodeSuite(t *testing.T) {

suite.Run(t, new(NodeSuite))
}
2 changes: 1 addition & 1 deletion tests/verifyincluded/verifyevents_azurite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (s *VerifyEventsSuite) TestVerifyIncludedMultiMassif() {
"verify-included",
})
s.NoError(err)
s.ReplaceStdIO() // reset stdin for write & close
s.ReplaceStdin() // reset stdin for write & close
}
})
}
Expand Down

0 comments on commit e5a03de

Please sign in to comment.