diff --git a/.github/workflows/go-unit-tests.yml b/.github/workflows/go-unit-tests.yml new file mode 100644 index 0000000..2b033e4 --- /dev/null +++ b/.github/workflows/go-unit-tests.yml @@ -0,0 +1,25 @@ +# This workflow will build a golang project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go + +name: Go Unit Tests + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: 1.19 + + - name: Test + run: go test --cover -v ./... diff --git a/README.md b/README.md index 3a7fa4b..96db3b1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ [![CodeQL](https://img.shields.io/github/actions/workflow/status/application-research/delta/codeql.yml?label=CodeQL&style=for-the-badge)](https://github.com/application-research/delta/actions/workflows/codeql.yml) +[![Unit Tests](https://img.shields.io/github/actions/workflow/status/application-research/delta/go-unit-tests.yml?label=UnitTests&style=for-the-badge)](https://github.com/application-research/delta/actions/workflows/go-unit-tests.yml) [![Release](https://img.shields.io/github/v/release/application-research/delta?display_name=release&style=for-the-badge)](https://github.com/application-research/delta/releases) # Δ Delta diff --git a/core/commp_test.go b/core/commp_test.go index bbbf762..db4deae 100644 --- a/core/commp_test.go +++ b/core/commp_test.go @@ -1,183 +1,97 @@ package core import ( + "bytes" "context" - "github.com/filecoin-project/go-commp-utils/writer" - "github.com/filecoin-project/go-state-types/abi" + "github.com/application-research/whypfs-core" "github.com/ipfs/go-cid" - blockstore "github.com/ipfs/go-ipfs-blockstore" carv2 "github.com/ipld/go-car/v2" - "io" - "reflect" "testing" -) -func TestCommpService_GenerateCommPCarV2(t *testing.T) { - type fields struct { - DeltaNode *DeltaNode - } - type args struct { - readerFromFile io.Reader - } - tests := []struct { - name string - fields fields - args args - want *abi.PieceInfo - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := CommpService{ - DeltaNode: tt.fields.DeltaNode, - } - got, err := c.GenerateCommPCarV2(tt.args.readerFromFile) - if (err != nil) != tt.wantErr { - t.Errorf("GenerateCommPCarV2() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("GenerateCommPCarV2() got = %v, want %v", got, tt.want) - } - }) - } -} + "github.com/stretchr/testify/assert" +) func TestCommpService_GenerateCommPFile(t *testing.T) { - type fields struct { - DeltaNode *DeltaNode - } - type args struct { - context context.Context - payloadCid cid.Cid - blockstore blockstore.Blockstore - } - tests := []struct { - name string - fields fields - args args - wantPieceCid cid.Cid - wantPayloadSize uint64 - wantUnPaddedPieceSize abi.UnpaddedPieceSize - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := CommpService{ - DeltaNode: tt.fields.DeltaNode, - } - gotPieceCid, gotPayloadSize, gotUnPaddedPieceSize, err := c.GenerateCommPFile(tt.args.context, tt.args.payloadCid, tt.args.blockstore) - if (err != nil) != tt.wantErr { - t.Errorf("GenerateCommPFile() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(gotPieceCid, tt.wantPieceCid) { - t.Errorf("GenerateCommPFile() gotPieceCid = %v, want %v", gotPieceCid, tt.wantPieceCid) - } - if gotPayloadSize != tt.wantPayloadSize { - t.Errorf("GenerateCommPFile() gotPayloadSize = %v, want %v", gotPayloadSize, tt.wantPayloadSize) - } - if gotUnPaddedPieceSize != tt.wantUnPaddedPieceSize { - t.Errorf("GenerateCommPFile() gotUnPaddedPieceSize = %v, want %v", gotUnPaddedPieceSize, tt.wantUnPaddedPieceSize) - } - }) + // create a mock DeltaNode instance + deltaNode := &DeltaNode{} + + // create a mock blockstore instance + params := whypfs.NewNodeParams{ + Ctx: context.Background(), + Datastore: whypfs.NewInMemoryDatastore(), + Repo: ".test", + } + whypfsPeer, err := whypfs.NewNode(params) + blockstore := whypfsPeer.Blockstore + + // create a CommpService instance using the mock DeltaNode instance + commpService := CommpService{ + DeltaNode: deltaNode, } + + // create a mock payload CID + payloadCid, _ := cid.Decode("bafy2bzacedx6vywq6so7e6m43g6op7dhcqkntwk2lrzvt6yljvf6xlzshxh5w") + + // call the GenerateCommPFile method + pieceCid, payloadSize, unPaddedPieceSize, err := commpService.GenerateCommPFile(context.Background(), payloadCid, blockstore) + + // assert that there is no error + assert.NoError(t, err) + + // assert that the piece CID, payload size, and unpadded piece size are not empty + assert.NotEmpty(t, pieceCid) + assert.NotEmpty(t, payloadSize) + assert.NotEmpty(t, unPaddedPieceSize) } -func TestCommpService_GenerateParallelCommp(t *testing.T) { - type fields struct { - DeltaNode *DeltaNode - } - type args struct { - readerFromFile io.Reader - } - tests := []struct { - name string - fields fields - args args - want writer.DataCIDSize - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := CommpService{ - DeltaNode: tt.fields.DeltaNode, - } - got, err := c.GenerateParallelCommp(tt.args.readerFromFile) - if (err != nil) != tt.wantErr { - t.Errorf("GenerateParallelCommp() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("GenerateParallelCommp() got = %v, want %v", got, tt.want) - } - }) - } +func TestCommpService_GetSize(t *testing.T) { + // create a mock payload reader + reader := bytes.NewReader([]byte("hello world")) + + // create a CommpService instance + commpService := CommpService{} + + // call the GetSize method + size := commpService.GetSize(reader) + + // assert that the size is correct + assert.Equal(t, 11, size) } func TestCommpService_GetCarSize(t *testing.T) { - type fields struct { - DeltaNode *DeltaNode - } - type args struct { - stream io.Reader - rd *carv2.Reader - } - tests := []struct { - name string - fields fields - args args - want int64 - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := CommpService{ - DeltaNode: tt.fields.DeltaNode, - } - got, err := c.GetCarSize(tt.args.stream, tt.args.rd) - if (err != nil) != tt.wantErr { - t.Errorf("GetCarSize() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("GetCarSize() got = %v, want %v", got, tt.want) - } - }) - } + // create a mock CARv2 file reader + reader := bytes.NewReader([]byte("file content")) + + // create a CARv2 reader from the mock CARv2 file reader + carReader, _ := carv2.NewReader(reader) + + // create a CommpService instance + commpService := CommpService{} + + // call the GetCarSize method + size, err := commpService.GetCarSize(reader, carReader) + + // assert that there is no error + assert.NoError(t, err) + + // assert that the size is correct + assert.Equal(t, int64(12), size) } -func TestCommpService_GetSize(t *testing.T) { - type fields struct { - DeltaNode *DeltaNode - } - type args struct { - stream io.Reader - } - tests := []struct { - name string - fields fields - args args - want int - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := CommpService{ - DeltaNode: tt.fields.DeltaNode, - } - if got := c.GetSize(tt.args.stream); got != tt.want { - t.Errorf("GetSize() = %v, want %v", got, tt.want) - } - }) - } +func TestCommpService_GenerateParallelCommp(t *testing.T) { + // create a mock payload reader + reader := bytes.NewReader([]byte("hello world")) + + // create a CommpService instance + commpService := CommpService{} + + // call the GenerateParallelCommp method + dataCIDSize, err := commpService.GenerateParallelCommp(reader) + + // assert that there is no error + assert.NoError(t, err) + + // assert that the Data CID and Size are not empty + assert.NotEmpty(t, dataCIDSize.PieceCID) + assert.NotEmpty(t, dataCIDSize.PieceSize) } diff --git a/core/deal.go b/core/deal.go new file mode 100644 index 0000000..bb5ce38 --- /dev/null +++ b/core/deal.go @@ -0,0 +1,6 @@ +package core + +// TODO: move core function of deal to this service +type DealService struct { + DeltaNode *DeltaNode +} diff --git a/core/job_test.go b/core/job_test.go index fc84cd4..bee505a 100644 --- a/core/job_test.go +++ b/core/job_test.go @@ -1,199 +1,104 @@ package core import ( - "reflect" + "errors" + "sync" "testing" ) -func TestCreateNewDispatcher(t *testing.T) { - tests := []struct { - name string - want *Dispatcher - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := CreateNewDispatcher(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("CreateNewDispatcher() = %v, want %v", got, tt.want) - } - }) - } +// MockProcessor is a mock implementation of the IProcessor interface +type MockProcessor struct { + err error } -func TestCreateNewWorker(t *testing.T) { - type args struct { - id int - workerQueue chan *Worker - jobQueue chan *Job - dStatus chan *DispatchStatus - } - tests := []struct { - name string - args args - want *Worker - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := CreateNewWorker(tt.args.id, tt.args.workerQueue, tt.args.jobQueue, tt.args.dStatus); !reflect.DeepEqual(got, tt.want) { - t.Errorf("CreateNewWorker() = %v, want %v", got, tt.want) - } - }) - } +func (m *MockProcessor) Run() error { + return m.err } -func TestDispatcher_AddJob(t *testing.T) { - type fields struct { - jobCounter int - jobQueue chan *Job - dispatchStatus chan *DispatchStatus - workQueue chan *Job - workerQueue chan *Worker - } - type args struct { - je IProcessor - } - tests := []struct { - name string - fields fields - args args - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - d := &Dispatcher{ - jobCounter: tt.fields.jobCounter, - jobQueue: tt.fields.jobQueue, - dispatchStatus: tt.fields.dispatchStatus, - workQueue: tt.fields.workQueue, - workerQueue: tt.fields.workerQueue, - } - d.AddJob(tt.args.je) - }) - } -} +func TestDispatcher(t *testing.T) { + // Create a new dispatcher + d := CreateNewDispatcher() -func TestDispatcher_AddJobAndDispatch(t *testing.T) { - type fields struct { - jobCounter int - jobQueue chan *Job - dispatchStatus chan *DispatchStatus - workQueue chan *Job - workerQueue chan *Worker - } - type args struct { - je IProcessor - numWorkers int + // Add some jobs to the dispatcher + numJobs := 5 + var wg sync.WaitGroup + for i := 0; i < numJobs; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + d.AddJob(&MockProcessor{}) + }(i) } - tests := []struct { - name string - fields fields - args args - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - d := &Dispatcher{ - jobCounter: tt.fields.jobCounter, - jobQueue: tt.fields.jobQueue, - dispatchStatus: tt.fields.dispatchStatus, - workQueue: tt.fields.workQueue, - workerQueue: tt.fields.workerQueue, - } - d.AddJobAndDispatch(tt.args.je, tt.args.numWorkers) - }) - } -} -func TestDispatcher_Finished(t *testing.T) { - type fields struct { - jobCounter int - jobQueue chan *Job - dispatchStatus chan *DispatchStatus - workQueue chan *Job - workerQueue chan *Worker - } - tests := []struct { - name string - fields fields - want bool - }{ - // TODO: Add test cases. + // Start the dispatcher with two workers + d.Start(2) + + // Wait for all jobs to finish + wg.Wait() + + // Check that there were no errors processing the jobs + if !d.Finished() { + t.Errorf("Dispatcher did not finish all jobs") } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - d := &Dispatcher{ - jobCounter: tt.fields.jobCounter, - jobQueue: tt.fields.jobQueue, - dispatchStatus: tt.fields.dispatchStatus, - workQueue: tt.fields.workQueue, - workerQueue: tt.fields.workerQueue, + + // Check that the dispatchStatus channel received the expected messages + var workerQuitCount, jobCount int + for { + select { + case ds := <-d.dispatchStatus: + if ds.Type == "worker" && ds.Status == "quit" { + workerQuitCount++ } - if got := d.Finished(); got != tt.want { - t.Errorf("Finished() = %v, want %v", got, tt.want) + if ds.Type == "job" && ds.Status == "done" { + jobCount++ } - }) - } -} - -func TestDispatcher_Start(t *testing.T) { - type fields struct { - jobCounter int - jobQueue chan *Job - dispatchStatus chan *DispatchStatus - workQueue chan *Job - workerQueue chan *Worker - } - type args struct { - numWorkers int - } - tests := []struct { - name string - fields fields - args args - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - d := &Dispatcher{ - jobCounter: tt.fields.jobCounter, - jobQueue: tt.fields.jobQueue, - dispatchStatus: tt.fields.dispatchStatus, - workQueue: tt.fields.workQueue, - workerQueue: tt.fields.workerQueue, + if workerQuitCount == 2 && jobCount == numJobs { + // Expected number of messages received + return } - d.Start(tt.args.numWorkers) - }) + default: + // No more messages in dispatchStatus channel + t.Errorf("Did not receive expected dispatch status messages") + return + } } } -func TestWorker_Start(t *testing.T) { - type fields struct { - ID int - jobs chan *Job - dispatchStatus chan *DispatchStatus - Quit chan bool - } - tests := []struct { - name string - fields fields - }{ - // TODO: Add test cases. +func TestJobWithError(t *testing.T) { + // Create a new dispatcher + d := CreateNewDispatcher() + + // Add a job with an error to the dispatcher + err := errors.New("job error") + d.AddJob(&MockProcessor{err}) + + // Start the dispatcher with one worker + d.Start(1) + + // Check that the dispatcher finished with an error + if !d.Finished() { + t.Errorf("Dispatcher did not finish all jobs") } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - w := &Worker{ - ID: tt.fields.ID, - jobs: tt.fields.jobs, - dispatchStatus: tt.fields.dispatchStatus, - Quit: tt.fields.Quit, + + // Check that the dispatchStatus channel received the expected messages + var workerQuitCount, jobCount int + for { + select { + case ds := <-d.dispatchStatus: + if ds.Type == "worker" && ds.Status == "quit" { + workerQuitCount++ + } + if ds.Type == "job" && ds.Status == "error" { + jobCount++ + } + if workerQuitCount == 1 && jobCount == 1 { + // Expected number of messages received + return } - w.Start() - }) + default: + // No more messages in dispatchStatus channel + t.Errorf("Did not receive expected dispatch status messages") + return + } } }