diff --git a/e2etest/newe2e_runazcopy_stdout.go b/e2etest/newe2e_runazcopy_stdout.go index 9c64e7069..9be719f28 100644 --- a/e2etest/newe2e_runazcopy_stdout.go +++ b/e2etest/newe2e_runazcopy_stdout.go @@ -11,6 +11,7 @@ var _ AzCopyStdout = &AzCopyParsedStdout{} var _ AzCopyStdout = &AzCopyParsedListStdout{} var _ AzCopyStdout = &AzCopyParsedCopySyncRemoveStdout{} var _ AzCopyStdout = &AzCopyParsedDryrunStdout{} +var _ AzCopyStdout = &AzCopyParsedJobsListStdout{} // ManySubscriberChannel is intended to reproduce the effects of .NET's events. // This allows us to *partially* answer the question of how we want to handle testing of prompting in the New E2E framework. @@ -194,3 +195,26 @@ func (a *AzCopyParsedDryrunStdout) Write(p []byte) (n int, err error) { return a.AzCopyParsedStdout.Write(p) } + +type AzCopyParsedJobsListStdout struct { + AzCopyParsedStdout + listenChan chan<- common.JsonOutputTemplate + JobsCount int +} + +func (a *AzCopyParsedJobsListStdout) Write(p []byte) (n int, err error) { + if a.listenChan == nil { + a.listenChan = a.OnParsedLine.SubscribeFunc(func(line common.JsonOutputTemplate) { + if line.MessageType == common.EOutputMessageType.EndOfJob().String() { + var tx common.ListJobsResponse + err = json.Unmarshal([]byte(line.MessageContent), &tx) + if err != nil { + return + } + + a.JobsCount = len(tx.JobIDDetails) + } + }) + } + return a.AzCopyParsedStdout.Write(p) +} diff --git a/e2etest/newe2e_task_runazcopy.go b/e2etest/newe2e_task_runazcopy.go index 1145c8aeb..3709be4bb 100644 --- a/e2etest/newe2e_task_runazcopy.go +++ b/e2etest/newe2e_task_runazcopy.go @@ -49,10 +49,11 @@ var _ AzCopyStdout = &AzCopyRawStdout{} type AzCopyVerb string const ( // initially supporting a limited set of verbs - AzCopyVerbCopy AzCopyVerb = "copy" - AzCopyVerbSync AzCopyVerb = "sync" - AzCopyVerbRemove AzCopyVerb = "remove" - AzCopyVerbList AzCopyVerb = "list" + AzCopyVerbCopy AzCopyVerb = "copy" + AzCopyVerbSync AzCopyVerb = "sync" + AzCopyVerbRemove AzCopyVerb = "remove" + AzCopyVerbList AzCopyVerb = "list" + AzCopyVerbJobsList AzCopyVerb = "jobs" ) type AzCopyTarget struct { @@ -89,7 +90,8 @@ func CreateAzCopyTarget(rm ResourceManager, authType ExplicitCredentialTypes, a } type AzCopyCommand struct { - Verb AzCopyVerb + Verb AzCopyVerb + PositionalArgs []string // Passing a ResourceManager assumes SAS (or GCP/S3) auth is intended. // Passing an AzCopyTarget will allow you to specify an exact credential type. // When OAuth, S3, GCP, AcctKey, etc. the appropriate env flags should auto-populate. @@ -249,6 +251,10 @@ func RunAzCopy(a ScenarioAsserter, commandSpec AzCopyCommand) (AzCopyStdout, *Az } out := []string{GlobalConfig.AzCopyExecutableConfig.ExecutablePath, string(commandSpec.Verb)} + + for _, v := range commandSpec.PositionalArgs { + out = append(out, v) + } for _, v := range commandSpec.Targets { out = append(out, commandSpec.applyTargetAuth(a, v)) @@ -290,6 +296,8 @@ func RunAzCopy(a ScenarioAsserter, commandSpec AzCopyCommand) (AzCopyStdout, *Az out = &AzCopyParsedCopySyncRemoveStdout{} case commandSpec.Verb == AzCopyVerbList: out = &AzCopyParsedListStdout{} + case commandSpec.Verb == AzCopyVerbJobsList: + out = &AzCopyParsedJobsListStdout{} default: // We don't know how to parse this. out = &AzCopyRawStdout{} } diff --git a/e2etest/newe2e_task_validation.go b/e2etest/newe2e_task_validation.go index 22b44943c..4d0c6fed3 100644 --- a/e2etest/newe2e_task_validation.go +++ b/e2etest/newe2e_task_validation.go @@ -5,14 +5,15 @@ import ( "encoding/base64" "encoding/hex" "fmt" - "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" - "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/lease" - "github.com/Azure/azure-storage-azcopy/v10/cmd" - "github.com/Azure/azure-storage-azcopy/v10/common" "io" "reflect" "strings" "time" + + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/lease" + "github.com/Azure/azure-storage-azcopy/v10/cmd" + "github.com/Azure/azure-storage-azcopy/v10/common" ) func ValidatePropertyPtr[T any](a Asserter, name string, expected, real *T) { @@ -308,3 +309,13 @@ func parseAzCopyListObject(a Asserter, line string) cmd.AzCopyListObject { ContentLength: properties["Content Length"], } } + +func ValidateJobsListOutput(a Asserter, stdout AzCopyStdout, expectedJobIDs int) { + if dryrunner, ok := a.(DryrunAsserter); ok && dryrunner.Dryrun() { + return + } + + jobsListStdout, ok := stdout.(*AzCopyParsedJobsListStdout) + a.AssertNow("stdout must be AzCopyParsedJobsListStdout", Equal{}, ok, true) + a.Assert("No of jobs executed should be equivalent", Equal{}, expectedJobIDs, jobsListStdout.JobsCount) +} diff --git a/e2etest/zt_newe2e_jobs_list_test.go b/e2etest/zt_newe2e_jobs_list_test.go new file mode 100644 index 000000000..b64bd775f --- /dev/null +++ b/e2etest/zt_newe2e_jobs_list_test.go @@ -0,0 +1,20 @@ +package e2etest + +func init() { + suiteManager.RegisterSuite(&JobsListSuite{}) +} + +type JobsListSuite struct{} + +func (s *JobsListSuite) Scenario_JobsListBasic(svm *ScenarioVariationManager) { + + jobsListOutput, _ := RunAzCopy( + svm, + AzCopyCommand{ + Verb: AzCopyVerbJobsList, + PositionalArgs: []string{"list"}, + Stdout: &AzCopyParsedJobsListStdout{}, + Flags: ListFlags{}, + }) + ValidateJobsListOutput(svm, jobsListOutput, 0) +} diff --git a/ste/JobPartPlan.go b/ste/JobPartPlan.go index 852b80805..f4302d4eb 100644 --- a/ste/JobPartPlan.go +++ b/ste/JobPartPlan.go @@ -2,10 +2,11 @@ package ste import ( "errors" - "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" "sync/atomic" "unsafe" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" + "github.com/Azure/azure-storage-azcopy/v10/common" ) @@ -142,8 +143,12 @@ func (jpph *JobPartPlanHeader) Transfer(transferIndex uint32) *JobPartPlanTransf // CommandString returns the command string given by user when job was created func (jpph *JobPartPlanHeader) CommandString() string { - data := unsafe.Pointer(uintptr(unsafe.Pointer(jpph)) + unsafe.Sizeof(*jpph)) // Address of Job Part Plan + Command String Length - return unsafe.String((*byte)(data), int(jpph.CommandStringLength)) + // Calculate the start address of the command string + start := uintptr(unsafe.Pointer(jpph)) + unsafe.Sizeof(*jpph) + + // Create a slice from the calculated start address + commandSlice := unsafe.Slice((*byte)(unsafe.Pointer(start)), int(jpph.CommandStringLength)) + return string(commandSlice) } func (jpph *JobPartPlanHeader) TransferSrcDstRelatives(transferIndex uint32) (relSource, relDest string) {