commit ff42d81ed53b26b9adf21df9618b77f17429c26a from: Sergey Bronnikov date: Sat Dec 19 21:22:52 2020 UTC Moved source code with backends support back commit - 544f91b1a634777e37327f09f162916807bbfea2 commit + ff42d81ed53b26b9adf21df9618b77f17429c26a blob - /dev/null blob + 66fd13c903cac02eb9657cd53fb227823484401d (mode 644) --- /dev/null +++ backends/.gitignore @@ -0,0 +1,15 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ blob - /dev/null blob + 7052f6db01b4e33bcaa870e1217606ac878a3386 (mode 644) --- /dev/null +++ backends/README.md @@ -0,0 +1,10 @@ +# testres-backends + +is a library to import test results. + +## Building + +``` +$ go get ./... +$ go test -v ./... +``` blob - /dev/null blob + 120c4a3c6414f3596d492fc3ad6b2013b039c6a6 (mode 644) --- /dev/null +++ backends/TODO.md @@ -0,0 +1,154 @@ +### TODO + +- конверторы для форматов в `formats/common.go` +- SubUnit V2 + - https://github.com/msgpack/msgpack-go/blob/master/unpack.go + - https://github.com/hashicorp/go-msgpack/blob/master/codec/decode.go +- импорт результатов только старше даты последнего результата из базы +- добавлять кастомные сертификаты для http клиента (`ca.go`) +- сохранять в структуре бранч, название пайплайна, коммиты +- поддержка Lava +- поддержка Cirrus CI +- опция `-limit` + +### GitHub Actions + +- https://help.github.com/en/actions/configuring-and-managing-workflows/persisting-workflow-data-using-artifacts +- https://developer.github.com/v3/actions/ +- ? https://github.com/google/go-github +- Documentation: https://docs.github.com/en/rest/reference/actions#artifacts + +### Zuul + +- Example: https://zuul.opendev.org/t/openstack/builds?project=openstack/glance +- Example: https://zuul.opendev.org/t/openstack/builds?project=openstack/ceilometer +- Example: https://zuul.opendev.org/t/openstack/builds?project=openstack/heat +- https://zuul.opendev.org/openapi + +### BitBucket + +- https://github.com/ktrysmt/go-bitbucket + +### Lava + +- https://staging.validation.linaro.org/api/help/ +- XML RPC Client https://github.com/kolo/xmlrpc + +### Kernel CI + +- https://api.kernelci.org +- https://github.com/kernelci/kernelci-backend +- Where is a Golang API? +- token is required + +### Codefresh + +- https://github.com/codefresh-io/go-sdk +- Example: https://github.com/nemequ/portable-snippets +- test results unavailable via API + +### Patchwork/Patchew + +- https://patchwork-freedesktop.readthedocs.io/en/latest/rest.html +- https://github.com/patchew-project/patchew +- Where is Golang API? + +### TestRail + +- http://docs.gurock.com/testrail-api2/start +- https://github.com/gurock/testrail-api +- Go: https://github.com/educlos/testrail +- Go: https://godoc.org/github.com/Etienne42/testrail +- https://secure.gurock.com/customers/testrail/trial/ +- github.com/educlos/testrail + +### Beaker + +- https://beaker-project.org/docs/server-api/ +- Where is Golang API? + +### BuildBot + +- REST API: https://github.com/buildbot/buildbot/blob/master/master/docs/developer/rest.rst +- Example: https://github.com/buildbot/buildbot/wiki/SuccessStories +- Example: http://buildbot.suricata-ids.org +- Example: https://buildbot.python.org/ +- Example: http://212.201.121.110:38010/ +- Example: https://buildbot.openinfosecfoundation.org/ +- Example: https://ci.chromium.org/p/chromium/g/main/console +- Example https://chromium.googlesource.com/infra/luci/luci-go/+/master/grpc/prpc/talk/buildbot/client/main.go +- LUCI: http://bit.ly/2kgyE9U +- https://docs.buildbot.net/latest/developer/rest.html +- "go.chromium.org/luci/grpc/prpc/talk/buildbot/proto" +- "go.chromium.org/luci/milo/buildsource/buildbot" +- "go.chromium.org/luci/milo/buildsource/buildbot/buildbotapi" +- "go.chromium.org/luci/milo/buildsource/buildbot/buildstore" + +### Drone CI + +- Publish test results in artifacts: https://github.com/drone/docs.drone.io/blob/master/artifacts.markdown +- Enterprise only: https://0-8-0.docs.drone.io/publish-unit-test-results/ +- API: https://docs.drone.io/api/endpoints/builds/build_list/ +- https://github.com/drone/drone/issues/239 +- Golang API: https://github.com/drone/drone-go + +### CDash + +- https://my.cdash.org/viewProjects.php +- https://www.paraview.org/Wiki/CDash:API +- https://open.cdash.org/viewProjects.php +- https://open.cdash.org/viewTest.php?buildid=6227968 +- https://open.cdash.org/viewTest.php?buildid=6227571 +- https://my.cdash.org/viewTest.php?buildid=1735823 +- Where is Golang API? + +### AWS CodePipeline + +- https://docs.aws.amazon.com/en_us/codepipeline/latest/userguide/welcome.html +- GoDoc: https://docs.aws.amazon.com/sdk-for-go/api/service/codepipeline/ +- GoDoc: https://docs.aws.amazon.com/sdk-for-go/api/service/codepipeline/#Artifact + +### Appveyor + +- API: https://www.appveyor.com/docs/api/projects-builds/ +- Can't access to test results via REST API https://github.com/appveyor/ci/issues/3226 +- Where is a Golang API? https://github.com/appveyor/ci/issues/3225 +- Example: https://ci.appveyor.com/project/rpcs3/rpcs3/branch/master/tests +- Example: https://ci.appveyor.com/project/dignifiedquire/deltachat-core-rust/branch/master/tests +- Example: https://ci.appveyor.com/project/quixdb/portable-snippets/branch/master + +### CodeShip CI + +- https://apidocs.codeship.com/v2/introduction/basic-vs-pro +- GoDoc: https://godoc.org/github.com/codeship/codeship-go#Build +- How to get a testing results? + +### Atlassian Bamboo + +- https://developer.atlassian.com/server/bamboo/rest-apis/ +- https://github.com/rcarmstrong/go-bamboo +- TODO: How to get test results? +- https://community.atlassian.com/t5/Answers-Developer-Questions/How-to-Get-Bamboo-Test-Results-via-REST/qaq-p/475715 + +### Concourse CI + +- https://ci.spearow.io/teams/main/pipelines/oregano/jobs/make-release/builds/30 + +### Report Portal + +- Where is an API documentation? https://github.com/reportportal/service-api/issues/1094 +- Go API https://github.com/avarabyeu/goRP +- Example: https://rp.epam.com/ui/ +- Example: http://web.demo.reportportal.io/ui/ +- https://github.com/ihar-kahadouski/dev-guide/blob/master/reporting.md + +### JetBrains Space + +- Golang API? +- Example? + +### Bitrise + +- https://api-docs.bitrise.io/ +- https://devcenter.bitrise.io/testing/test-reports/ +- https://devcenter.bitrise.io/testing/exporting-to-test-reports-from-custom-script-steps/ blob - /dev/null blob + 79e8b10a05801db5d59d6b5720e1f783c877e0bd (mode 644) --- /dev/null +++ backends/backend_azure_devops.go @@ -0,0 +1,180 @@ +package backends + +import ( + "context" + "github.com/microsoft/azure-devops-go-api/azuredevops" + "github.com/microsoft/azure-devops-go-api/azuredevops/build" + "github.com/microsoft/azure-devops-go-api/azuredevops/core" + "github.com/microsoft/azure-devops-go-api/azuredevops/pipelines" + "github.com/microsoft/azure-devops-go-api/azuredevops/testresults" + "github.com/ligurio/testres-db/formats" + "log" + "net/http" +) + +func getBuilds(ctx context.Context, connection *azuredevops.Connection, ProjectName *string, BranchName *string) (*[]build.Build, error) { + buildClient, err := build.NewClient(ctx, connection) + if err != nil { + return nil, err + } + + buildsArgs := build.GetBuildsArgs{Project: ProjectName} + if *BranchName != "" { + buildsArgs.BranchName = BranchName + } + responseValue, err := buildClient.GetBuilds(ctx, buildsArgs) + if err != nil { + return nil, err + } + + var builds *[]build.Build = nil + builds = &(*responseValue).Value + for responseValue != nil { + // FIXME: builds = append(*builds, &(*responseValue).Value) + for _, teamBuildReference := range (*responseValue).Value { + log.Printf("Build %v, %s", *teamBuildReference.BuildNumber, *teamBuildReference.SourceBranch) + } + + if responseValue.ContinuationToken != "" { + buildsArgs := build.GetBuildsArgs{ + ContinuationToken: &responseValue.ContinuationToken, + } + buildsArgs.ContinuationToken = &responseValue.ContinuationToken + responseValue, err = buildClient.GetBuilds(ctx, buildsArgs) + if err != nil { + return nil, err + } + } else { + responseValue = nil + } + } + + testresultsClient := testresults.NewClient(ctx, connection) + for _, bld := range *builds { + log.Println("Build Num", *bld.Id) + TestResultDetailsForBuildArgs := testresults.GetTestResultDetailsForBuildArgs{Project: ProjectName, BuildId: bld.Id} + TestResults, err := testresultsClient.GetTestResultDetailsForBuild(ctx, TestResultDetailsForBuildArgs) + if err != nil { + log.Println("Unsupported?", err) + continue + } + if TestResults != nil { + log.Println("TestResults", (*TestResults).GroupByField) + // log.Println("TestResults #%v", (*TestResults).ResultsForGroup) + } + } + + return builds, err +} + +func getPipelineRef(ctx context.Context, connection *azuredevops.Connection, Project *string, PipelineName *string) (*pipelines.Pipeline, error) { + pipelineClient := pipelines.NewClient(ctx, connection) + responseValue, err := pipelineClient.ListPipelines(ctx, pipelines.ListPipelinesArgs{Project: Project}) + if err != nil { + return nil, err + } + + var PipelineRef *pipelines.Pipeline = nil + for _, teamPipelineReference := range (*responseValue).Value { + // log.Printf("Pipeline = %v", *teamPipelineReference.Name) + if *teamPipelineReference.Name == *PipelineName { + PipelineRef = &teamPipelineReference + break + } + } + + return PipelineRef, nil +} + +func getProjectRef(ctx context.Context, connection *azuredevops.Connection, ProjectName *string) (*core.TeamProjectReference, error) { + coreClient, err := core.NewClient(ctx, connection) + if err != nil { + return nil, err + } + + responseValue, err := coreClient.GetProjects(ctx, core.GetProjectsArgs{}) + if err != nil { + return nil, err + } + + index := 0 + var Project *core.TeamProjectReference = nil + for responseValue != nil { + for _, teamProjectReference := range (*responseValue).Value { + // log.Printf("Name[%v] = %v", index, *teamProjectReference.Name) + if *teamProjectReference.Name == *ProjectName { + Project = &teamProjectReference + break + } + index++ + } + + if responseValue.ContinuationToken != "" { + projectArgs := core.GetProjectsArgs{ + ContinuationToken: &responseValue.ContinuationToken, + } + responseValue, err = coreClient.GetProjects(ctx, projectArgs) + if err != nil { + return nil, err + } + } else { + responseValue = nil + } + } + + return Project, nil + +} + +// Using custom http client: https://github.com/microsoft/azure-devops-go-api/issues/52 +func SyncAzureDevOps(client *http.Client, b *Backend) (*[]formats.TestResult, error) { + if b.Username != "" { + log.Println("Username is specified but unused", b.Username) + } + + connection := azuredevops.NewPatConnection(b.Base, b.Secret) + ctx := context.Background() + + project, err := getProjectRef(ctx, connection, &b.Project) + if err != nil { + return nil, err + } + if project.Url != nil { + log.Println("URL:", *project.Url) + } + log.Println("Last Update Time:", project.LastUpdateTime) + + if project.Abbreviation != nil { + log.Println(*project.Abbreviation) + } + + pipeline, err := getPipelineRef(ctx, connection, &b.Project, &b.Pipeline) + if err != nil { + return nil, err + } + log.Println("Pipeline:", *pipeline.Name) + builds, err := getBuilds(ctx, connection, project.Name, &b.Branch) + if err != nil { + return nil, err + } + + if builds == nil { + log.Println("list of builds is empty") + return nil, err + } + for _, bld := range *builds { + log.Println("Build", *bld.BuildNumber, *bld.SourceBranch) + } + + return nil, nil +} + +/* +http://localhost:6060/pkg/github.com/microsoft/azure-devops-go-api/azuredevops/testplan/ +http://localhost:6060/pkg/github.com/microsoft/azure-devops-go-api/azuredevops/test/ +http://localhost:6060/pkg/github.com/microsoft/azure-devops-go-api/azuredevops/testresults/ +func AzureDevopsTMS_fn(b *Backend) ([]*formats.TestReport, error) { + log.Println("not implemented") + return nil, nil +} +*/ blob - /dev/null blob + 34bd3fdcf20c351369628222c8df79b84e535e49 (mode 644) --- /dev/null +++ backends/backend_azure_devops_test.go @@ -0,0 +1,10 @@ +// https://github.com/drone/drone-go/blob/master/drone/client_test.go +// https://github.com/ktrysmt/go-bitbucket/blob/master/tests/repository_test.go + +package backends + +import "testing" + +func TestSyncAzureDevOps(t *testing.T) { + t.Log("TestSyncAzureDevOps") +} blob - /dev/null blob + 2a33f857ce99c99d81cbb498d987ab8d6b61fac5 (mode 644) --- /dev/null +++ backends/backend_circleci.go @@ -0,0 +1,48 @@ +package backends + +// Documentation: https://circleci.com/docs/2.0/artifacts/ + +import ( + "github.com/jszwedko/go-circleci" + "github.com/ligurio/testres-db/formats" + "log" + "net/http" + "strings" +) + +func SyncCircleCI(client *http.Client, b *Backend) (*[]formats.TestResult, error) { + project_path := strings.Split(b.Project, "/") + if len(project_path) != 2 { + log.Println("Perhaps wrong project name specified") + } + + account := project_path[0] + repo := project_path[1] + + connection := &circleci.Client{Token: b.Secret, HTTPClient: client, Debug: true} + builds, err := connection.ListRecentBuildsForProject(account, repo, b.Branch, "", -1, 0) + if err != nil { + log.Println(err) + return nil, err + } + + for _, build := range builds { + log.Printf("Found build: %d, status %s\n", build.BuildNum, build.Status) + metadata, err := connection.ListTestMetadata(account, repo, build.BuildNum) + if err != nil { + log.Println(err) + return nil, err + } + + artifacts, err := connection.ListBuildArtifacts(account, repo, build.BuildNum) + for _, artifact := range artifacts { + log.Println("Found artifact:", artifact.URL) + } + + for _, test := range metadata { + log.Println("Found test:", test.Result, test.Name, test.RunTime) + } + } + + return nil, nil +} blob - /dev/null blob + 036bd24a3d6f4d2c4daf823e22c49e5c090194fe (mode 644) --- /dev/null +++ backends/backend_circleci_test.go @@ -0,0 +1,10 @@ +// https://github.com/drone/drone-go/blob/master/drone/client_test.go +// https://github.com/ktrysmt/go-bitbucket/blob/master/tests/repository_test.go + +package backends + +import "testing" + +func TestSyncCircleCI(t *testing.T) { + t.Log("TestSyncCircleCI") +} blob - /dev/null blob + b8a4f49dd4208ff17e87a571d3c1476e4fef540e (mode 644) --- /dev/null +++ backends/backend_cirrusci.go @@ -0,0 +1,129 @@ +// https://cirrus-ci.org/api/ +// https://github.com/cirruslabs/cirrus-ci-web/blob/master/schema.graphql + +/* + +#!/bin/sh + +# https://github.com/cirruslabs/cirrus-ci-web/blob/master/schema.graphql + +curl -s -X POST --data \ +'{ + "query": "query BuildBySHAQuery($owner: String!, $name: String!, $SHA: String) { searchBuilds(repositoryOwner: $owner, repositoryName: $name, SHA: $SHA) { id } }", + "variables": { + "owner": "qemu", + "name": "qemu", + "SHA": "43d1455cf84283466e5c22a217db5ef4b8197b14" + } +}' \ +https://api.cirrus-ci.com/graphql | python -m json.tool +*/ + +package backends + +import ( + "github.com/machinebox/graphql" + "github.com/ligurio/testres-db/formats" + "golang.org/x/net/context" + "net/http" +) + +func SyncCirrusCI(client *http.Client, b *Backend) (*[]formats.TestResult, error) { + graphql_scheme := "https://api.cirrus-ci.com/graphql" + ClientOption := graphql.WithHTTPClient(client) + connection := graphql.NewClient(graphql_scheme, ClientOption) + request := "" + req := graphql.NewRequest(request) + + type response struct { + Name string + Items struct { + Records []struct { + Title string + } + } + } + + var respData response + ctx := context.Background() + if err := connection.Run(ctx, req, &respData); err != nil { + return nil, err + } + + return nil, nil +} + +/* +type Root { + viewer: User + repository(id: ID!): Repository + githubRepository(owner: String!, name: String!): Repository + githubRepositories(owner: String!): [Repository] + githubOrganizationInfo(organization: String!): GitHubOrganizationInfo + build(id: ID!): Build + searchBuilds(repositoryOwner: String!, repositoryName: String!, SHA: String): [Build] + task(id: ID!): Task + webhookDelivery(id: String!): WebHookDelivery +} + +type Build { + id: ID! + repositoryId: ID! + branch: String! + changeIdInRepo: String! + changeMessageTitle: String + changeMessage: String + durationInSeconds: Int + clockDurationInSeconds: Int + pullRequest: Int + checkSuiteId: Int + isSenderUserCollaborator: Boolean + senderUserPermissions: String + changeTimestamp: Int! + buildCreatedTimestamp: Int! + status: BuildStatus + notifications: [Notification] + tasks: [Task] + taskGroupsAmount: Int + latestGroupTasks: [Task] + repository: Repository! + viewerPermission: PermissionType! +} + +type Task { + id: ID! + buildId: ID! + repositoryId: ID! + name: String! + status: TaskStatus + notifications: [Notification] + commands: [TaskCommand] + artifacts: [Artifacts] + commandLogsTail(name: String!): [String] + statusTimestamp: Int! + creationTimestamp: Int! + scheduledTimestamp: Int! + executingTimestamp: Int! + finalStatusTimestamp: Int! + durationInSeconds: Int! + labels: [String] + uniqueLabels: [String] + requiredPRLabels: [String] + optional: Boolean + statusDurations: [TaskStatusDuration] + repository: Repository! + build: Build! + previousRuns: [Task] + allOtherRuns: [Task] + dependencies: [Task] + automaticReRun: Boolean! + useComputeCredits: Boolean! + usedComputeCredits: Boolean! + transaction: AccountTransaction + triggerType: TaskTriggerType! + instanceResources: InstanceResources +} + + + +*/ blob - /dev/null blob + 500df8262601f0e5a8f62822b1c6e6a77789f59d (mode 644) --- /dev/null +++ backends/backend_cirrusci_test.go @@ -0,0 +1,10 @@ +// https://github.com/drone/drone-go/blob/master/drone/client_test.go +// https://github.com/ktrysmt/go-bitbucket/blob/master/tests/repository_test.go + +package backends + +import "testing" + +func TestSyncCirrusCI(t *testing.T) { + t.Log("TestSyncCirrusCI") +} blob - /dev/null blob + 0e2d89eb711612206f1df7348fa41e333d7aad21 (mode 644) --- /dev/null +++ backends/backend_common.go @@ -0,0 +1,85 @@ +package backends + +import ( + "crypto/tls" + "errors" + "github.com/ligurio/testres-db/formats" + "io" + "log" + "net/http" + "os" +) + +type fnSyncBackend func(client *http.Client, b *Backend) (*[]formats.TestResult, error) + +var backend = map[string]fnSyncBackend{ + "azure_devops": SyncAzureDevOps, + "circleci": SyncCircleCI, + "cirrusci": SyncCirrusCI, + "gitlab": SyncGitLab, + "jenkins": SyncJenkins, + "teamcity": SyncTeamCity, + "travisci": SyncTravisCI, +} + +type Backend struct { + Name string + Base string + Project string + Branch string + Username string + Secret string + Pipeline string + Type string + Artifacts []Artifact +} + +type Artifact struct { + Path string +} + +var ( + errUnknownBackend = errors.New("Unknown backend") +) + +// https://stackoverflow.com/questions/38822764/how-to-send-a-https-request-with-a-certificate-golang/38825553#38825553 +func NewAPIClient() *http.Client { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + client := &http.Client{Transport: tr} + + return client +} + +func (b *Backend) GetTestResults() (*[]formats.TestResult, error) { + log.Println("Backend:", b.Type) + fn := backend[b.Type] + if fn == nil { + return nil, errUnknownBackend + } + client := NewAPIClient() + result, err := fn(client, b) + if err != nil { + return nil, err + } + + return result, nil +} + +func DownloadFile(filename string, url string) error { + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + out, err := os.Create(filename) + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, resp.Body) + return err +} blob - /dev/null blob + 8cf732270551d2c04e4c75558243d22f4efac9fa (mode 644) --- /dev/null +++ backends/backend_gitlab.go @@ -0,0 +1,94 @@ +package backends + +import ( + "fmt" + "github.com/ligurio/testres-db/formats" + gitlab "github.com/xanzy/go-gitlab" + "log" + "net/http" + "path/filepath" +) + +func SyncGitLab(client *http.Client, b *Backend) (*[]formats.TestResult, error) { + if b.Pipeline != "" { + log.Println("Option pipeline is specified, but unused") + } + + gl := gitlab.NewClient(client, b.Secret) + gl.SetBaseURL(b.Base) + + projOpt := &gitlab.GetProjectOptions{ + Statistics: gitlab.Bool(false), + License: gitlab.Bool(false), + WithCustomAttributes: gitlab.Bool(false), + } + + p, _, err := gl.Projects.GetProject(b.Project, projOpt, nil) + if err != nil { + return nil, err + } + + /* + + const ( + Pending BuildStateValue = "pending" + Running BuildStateValue = "running" + Success BuildStateValue = "success" + Failed BuildStateValue = "failed" + Canceled BuildStateValue = "canceled" + Skipped BuildStateValue = "skipped" + Manual BuildStateValue = "manual" + ) + */ + + projectOpt := &gitlab.ListProjectPipelinesOptions{ + Scope: gitlab.String("finished"), + Status: gitlab.BuildState(gitlab.Success), // FIXME: use at least failed status + Ref: gitlab.String(b.Branch), + OrderBy: gitlab.String("updated_at"), + Sort: gitlab.String("asc"), + } + + pipelines, _, err := gl.Pipelines.ListProjectPipelines(p.ID, projectOpt) + if err != nil { + return nil, err + } + + jobsOpt := &gitlab.ListJobsOptions{ + ListOptions: gitlab.ListOptions{Page: 1, PerPage: 10}, + Scope: []gitlab.BuildStateValue{"created", "pending", "running", "failed", "success", "canceled", "skipped"}, + } + for _, pipeline := range pipelines { + log.Printf("Found pipeline: %d, status %s", pipeline.ID, pipeline.Status) + log.Printf("SHA %s, Ref %s", pipeline.SHA, pipeline.Ref) + jobs, _, err := gl.Jobs.ListPipelineJobs(p.ID, pipeline.ID, jobsOpt, nil) + if err != nil { + log.Println(err) + continue + } + for _, job := range jobs { + log.Println("Found job", job.ID, job.Name, job.Status) + for _, artifact := range job.Artifacts { + log.Printf("Found file %s (%d)", artifact.Filename, artifact.Size) + fnParse := formats.Parser[filepath.Ext(artifact.Filename)] + if fnParse == nil { + continue + } + fileUrl := artifact.Filename + if err := DownloadFile(artifact.Filename, fileUrl); err != nil { + log.Println(err) + continue + } + report, err := fnParse(artifact.Filename) + if err != nil { + log.Println(err) + continue + } + report.Name = fmt.Sprintf("%d", job.ID) + /* FIXME: report.CreatedAt = job.CreatedAt */ + } + } + } + + return nil, nil +} blob - /dev/null blob + 59625003d2687755f49cb819f727e227e253567c (mode 644) --- /dev/null +++ backends/backend_gitlab_test.go @@ -0,0 +1,9 @@ +// https://github.com/drone/drone-go/blob/master/drone/client_test.go +// https://github.com/ktrysmt/go-bitbucket/blob/master/tests/repository_test.go +package backends + +import "testing" + +func TestSyncGitLab(t *testing.T) { + t.Log("TestSyncGitLab") +} blob - /dev/null blob + efaa13f34f77727574619f5b1c99af0208c27adc (mode 644) --- /dev/null +++ backends/backend_jenkins.go @@ -0,0 +1,48 @@ +package backends + +import ( + "github.com/bndr/gojenkins" + "github.com/ligurio/testres-db/formats" + "log" + "net/http" +) + +func SyncJenkins(client *http.Client, b *Backend) (*[]formats.TestResult, error) { + var jenkins *gojenkins.Jenkins + jenkins = gojenkins.CreateJenkins(client, b.Base, b.Username, b.Secret) + _, err := jenkins.Init() + if err != nil { + return nil, err + } + + jobBuilds, err := jenkins.GetAllBuildIds(b.Pipeline) + if err != nil { + return nil, err + } + + results := make([]formats.TestResult, len(jobBuilds)) + for _, jobBuild := range jobBuilds { + buildNum, err := jenkins.GetBuild(b.Pipeline, jobBuild.Number) + if err != nil { + return &results, err + } + log.Println(jobBuild.URL, buildNum.GetResult()) + TestResult, err := buildNum.GetResultSet() + if err != nil { + return &results, err + } + var testcases []formats.TestCase + for _, suite := range TestResult.Suites { + var testcase formats.TestCase + for _, test := range suite.Cases { + testcase = formats.TestCase{Name: test.Name} + } + testcases = append(testcases, testcase) + } + buildInfo := buildNum.Info() + var result = formats.TestResult{Name: buildInfo.ID, TestCases: testcases} + results = append(results, result) + } + + return &results, nil +} blob - /dev/null blob + 2cd221cd08cb22dae115b87846d232691ab88dad (mode 644) --- /dev/null +++ backends/backend_jenkins_test.go @@ -0,0 +1,9 @@ +// https://github.com/drone/drone-go/blob/master/drone/client_test.go +// https://github.com/ktrysmt/go-bitbucket/blob/master/tests/repository_test.go +package backends + +import "testing" + +func TestSyncJenkins(t *testing.T) { + t.Log("TestSyncJenkins") +} blob - /dev/null blob + c4e4e043f581325d356cf695e7824fe7d5f735a5 (mode 644) --- /dev/null +++ backends/backend_teamcity.go @@ -0,0 +1,27 @@ +// https://confluence.jetbrains.com/display/TCD10/REST+API +// https://www.jetbrains.com/help/teamcity/rest-api.html + +package backends + +import ( + teamcity "github.com/cvbarros/go-teamcity/teamcity" + "github.com/ligurio/testres-db/formats" + "log" + "net/http" +) + +func SyncTeamCity(client *http.Client, b *Backend) (*[]formats.TestResult, error) { + connection, err := teamcity.NewWithAddress(b.Username, b.Secret, b.Base, client) + if err != nil { + return nil, err + } + project, _ := connection.Projects.GetByID("TestNG_BuildTestsWithGradle") + if err != nil { + return nil, err + } + log.Println(project) + + // https://godoc.org/github.com/abourget/teamcity#TestOccurrence + + return nil, nil +} blob - /dev/null blob + e1efe0e5634409fbcdfcc6180be7e6e71a6e23d1 (mode 644) --- /dev/null +++ backends/backend_teamcity_test.go @@ -0,0 +1,9 @@ +// https://github.com/drone/drone-go/blob/master/drone/client_test.go +// https://github.com/ktrysmt/go-bitbucket/blob/master/tests/repository_test.go +package backends + +import "testing" + +func TestSyncTeamCityCI(t *testing.T) { + t.Log("TestSyncTeamCityCI") +} blob - /dev/null blob + 695ea07b5ddc4358ce4b62f515533e69bda9e3d7 (mode 644) --- /dev/null +++ backends/backend_travisci.go @@ -0,0 +1,51 @@ +package backends + +import ( + "context" + "fmt" + "github.com/ligurio/testres-db/formats" + travisci "github.com/shuheiktgw/go-travis" + "log" + "net/http" + "path" +) + +func SyncTravisCI(client *http.Client, b *Backend) (*[]formats.TestResult, error) { + connection := travisci.NewClient(b.Base, b.Secret) + connection.HTTPClient = client + build_service := connection.Builds + + ctx := context.Background() + var options travisci.BuildsOption + builds, _, err := build_service.List(ctx, &options) + if err != nil { + return nil, err + } + + results := make([]formats.TestResult, len(builds)) + baseURL := "https://travis-ci.org/" + for _, build := range builds { + // https://godoc.org/github.com/shuheiktgw/go-travis#Build + metadata := *build.Metadata + log.Printf("Found build: %s, status %s\n", path.Join(baseURL, b.Pipeline, *metadata.Href), *build.State) + buildId := fmt.Sprintf("%d", build.Id) + var testcases []formats.TestCase + var result = formats.TestResult{Name: buildId} + log.Println("BuildOn", *build.FinishedAt) + //var result = formats.TestResult{Name: buildId, CreatedAt: *build.FinishedAt} + var testcase formats.TestCase + for _, job := range build.Jobs { + // https://godoc.org/github.com/shuheiktgw/go-travis#Job + if job.State == nil { + continue + } + log.Println("Job ID: ", *job.Id) + testcase = formats.TestCase{Name: "XXX"} + } + testcases = append(testcases, testcase) + result.TestCases = testcases + results = append(results, result) + } + + return nil, nil +} blob - /dev/null blob + 742a81166b6218919691604be82fc1f745955785 (mode 644) --- /dev/null +++ backends/client_test.go_ @@ -0,0 +1,879 @@ +package drone + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + "github.com/google/go-cmp/cmp" +) + +// +// user tests. +// + +func TestSelf(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(mockHandler)) + defer ts.Close() + + client := New(ts.URL) + got, err := client.Self() + if err != nil { + t.Error(err) + return + } + + in, err := ioutil.ReadFile("testdata/user.json.golden") + if err != nil { + t.Error(err) + return + } + want := new(User) + err = json.Unmarshal(in, want) + if err != nil { + t.Error(err) + return + } + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("Unexpected response") + t.Log(diff) + } +} + +func TestUser(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(mockHandler)) + defer ts.Close() + + client := New(ts.URL) + got, err := client.User("octocat") + if err != nil { + t.Error(err) + return + } + + in, err := ioutil.ReadFile("testdata/user.json.golden") + if err != nil { + t.Error(err) + return + } + want := new(User) + err = json.Unmarshal(in, want) + if err != nil { + t.Error(err) + return + } + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("Unexpected response") + t.Log(diff) + } +} + +func TestUserList(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(mockHandler)) + defer ts.Close() + + client := New(ts.URL) + got, err := client.UserList() + if err != nil { + t.Error(err) + return + } + + in, err := ioutil.ReadFile("testdata/users.json.golden") + if err != nil { + t.Error(err) + return + } + want := []*User{} + err = json.Unmarshal(in, &want) + if err != nil { + t.Error(err) + return + } + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("Unexpected response") + t.Log(diff) + } +} + +func TestUserDelete(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(mockHandler)) + defer ts.Close() + + client := New(ts.URL) + err := client.UserDelete("octocat") + if err != nil { + t.Error(err) + return + } +} + +func TestUserCreate(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(mockHandler)) + defer ts.Close() + + client := New(ts.URL) + got, err := client.UserCreate(&User{}) + if err != nil { + t.Error(err) + return + } + + in, err := ioutil.ReadFile("testdata/user.json.golden") + if err != nil { + t.Error(err) + return + } + want := new(User) + err = json.Unmarshal(in, want) + if err != nil { + t.Error(err) + return + } + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("Unexpected response") + t.Log(diff) + } +} + +func TestUserUpdate(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(mockHandler)) + defer ts.Close() + + client := New(ts.URL) + got, err := client.UserUpdate("octocat", &UserPatch{}) + if err != nil { + t.Error(err) + return + } + + in, err := ioutil.ReadFile("testdata/user.json.golden") + if err != nil { + t.Error(err) + return + } + want := new(User) + err = json.Unmarshal(in, want) + if err != nil { + t.Error(err) + return + } + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("Unexpected response") + t.Log(diff) + } +} + +// +// repos +// + +func TestRepo(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(mockHandler)) + defer ts.Close() + + client := New(ts.URL) + got, err := client.Repo("octocat", "hello-world") + if err != nil { + t.Error(err) + return + } + + in, err := ioutil.ReadFile("testdata/repo.json.golden") + if err != nil { + t.Error(err) + return + } + want := new(Repo) + err = json.Unmarshal(in, want) + if err != nil { + t.Error(err) + return + } + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("Unexpected response") + t.Log(diff) + } +} + +func TestRepoList(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(mockHandler)) + defer ts.Close() + + client := New(ts.URL) + got, err := client.RepoList() + if err != nil { + t.Error(err) + return + } + + in, err := ioutil.ReadFile("testdata/repos.json.golden") + if err != nil { + t.Error(err) + return + } + want := []*Repo{} + err = json.Unmarshal(in, &want) + if err != nil { + t.Error(err) + return + } + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("Unexpected response") + t.Log(diff) + } +} + +func TestRepoListSync(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(mockHandler)) + defer ts.Close() + + client := New(ts.URL) + got, err := client.RepoListSync() + if err != nil { + t.Error(err) + return + } + + in, err := ioutil.ReadFile("testdata/repos.json.golden") + if err != nil { + t.Error(err) + return + } + want := []*Repo{} + err = json.Unmarshal(in, &want) + if err != nil { + t.Error(err) + return + } + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("Unexpected response") + t.Log(diff) + } +} + +func TestRepoEnable(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(mockHandler)) + defer ts.Close() + + client := New(ts.URL) + got, err := client.RepoEnable("octocat", "hello-world") + if err != nil { + t.Error(err) + return + } + + in, err := ioutil.ReadFile("testdata/repo.json.golden") + if err != nil { + t.Error(err) + return + } + want := new(Repo) + err = json.Unmarshal(in, want) + if err != nil { + t.Error(err) + return + } + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("Unexpected response") + t.Log(diff) + } +} + +func TestRepoDisable(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(mockHandler)) + defer ts.Close() + + client := New(ts.URL) + err := client.RepoDisable("octocat", "hello-world") + if err != nil { + t.Error(err) + return + } +} + +func TestRepoRepair(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(mockHandler)) + defer ts.Close() + + client := New(ts.URL) + err := client.RepoRepair("octocat", "hello-world") + if err != nil { + t.Error(err) + return + } +} + +func TestRepoChown(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(mockHandler)) + defer ts.Close() + + client := New(ts.URL) + got, err := client.RepoChown("octocat", "hello-world") + if err != nil { + t.Error(err) + return + } + + in, err := ioutil.ReadFile("testdata/repo.json.golden") + if err != nil { + t.Error(err) + return + } + want := new(Repo) + err = json.Unmarshal(in, want) + if err != nil { + t.Error(err) + return + } + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("Unexpected response") + t.Log(diff) + } +} + +func TestRepoUpdate(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(mockHandler)) + defer ts.Close() + + client := New(ts.URL) + got, err := client.RepoUpdate("octocat", "hello-world", &RepoPatch{}) + if err != nil { + t.Error(err) + return + } + + in, err := ioutil.ReadFile("testdata/repo.json.golden") + if err != nil { + t.Error(err) + return + } + want := new(Repo) + err = json.Unmarshal(in, want) + if err != nil { + t.Error(err) + return + } + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("Unexpected response") + t.Log(diff) + } +} + +// +// cron jobs +// + +func TestCron(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(mockHandler)) + defer ts.Close() + + client := New(ts.URL) + got, err := client.Cron("octocat", "hello-world", "nightly") + if err != nil { + t.Error(err) + return + } + + in, err := ioutil.ReadFile("testdata/cron.json.golden") + if err != nil { + t.Error(err) + return + } + want := new(Cron) + err = json.Unmarshal(in, want) + if err != nil { + t.Error(err) + return + } + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("Unexpected response") + t.Log(diff) + } +} + +func TestCronList(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(mockHandler)) + defer ts.Close() + + client := New(ts.URL) + got, err := client.CronList("octocat", "hello-world") + if err != nil { + t.Error(err) + return + } + + in, err := ioutil.ReadFile("testdata/crons.json.golden") + if err != nil { + t.Error(err) + return + } + want := []*Cron{} + err = json.Unmarshal(in, &want) + if err != nil { + t.Error(err) + return + } + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("Unexpected response") + t.Log(diff) + } +} + +// func TestCronDisable(t *testing.T) { +// ts := httptest.NewServer(http.HandlerFunc(mockHandler)) +// defer ts.Close() + +// client := New(ts.URL) +// err := client.CronDisable("octocat", "hello-world", "nightly") +// if err != nil { +// t.Error(err) +// return +// } +// } + +// func TestCronEnable(t *testing.T) { +// ts := httptest.NewServer(http.HandlerFunc(mockHandler)) +// defer ts.Close() + +// client := New(ts.URL) +// err := client.CronEnable("octocat", "hello-world", "nightly") +// if err != nil { +// t.Error(err) +// return +// } +// } + +// +// builds +// + +func TestBuild(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(mockHandler)) + defer ts.Close() + + client := New(ts.URL) + got, err := client.Build("octocat", "hello-world", 1) + if err != nil { + t.Error(err) + return + } + + in, err := ioutil.ReadFile("testdata/build.json.golden") + if err != nil { + t.Error(err) + return + } + want := new(Build) + err = json.Unmarshal(in, want) + if err != nil { + t.Error(err) + return + } + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("Unexpected response") + t.Log(diff) + } +} + +func TestBuildLast(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(mockHandler)) + defer ts.Close() + + client := New(ts.URL) + got, err := client.BuildLast("octocat", "hello-world", "master") + if err != nil { + t.Error(err) + return + } + + in, err := ioutil.ReadFile("testdata/build.json.golden") + if err != nil { + t.Error(err) + return + } + want := new(Build) + err = json.Unmarshal(in, want) + if err != nil { + t.Error(err) + return + } + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("Unexpected response") + t.Log(diff) + } +} + +func TestBuildList(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(mockHandler)) + defer ts.Close() + + client := New(ts.URL) + got, err := client.BuildList("octocat", "hello-world", ListOptions{}) + if err != nil { + t.Error(err) + return + } + + in, err := ioutil.ReadFile("testdata/builds.json.golden") + if err != nil { + t.Error(err) + return + } + want := []*Build{} + err = json.Unmarshal(in, &want) + if err != nil { + t.Error(err) + return + } + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("Unexpected response") + t.Log(diff) + } +} + +// func TestBuildQueue(t *testing.T) { +// ts := httptest.NewServer(http.HandlerFunc(mockHandler)) +// defer ts.Close() + +// client := New(ts.URL) +// got, err := client.BuildQueue() +// if err != nil { +// t.Error(err) +// return +// } + +// in, err := ioutil.ReadFile("testdata/builds.json.golden") +// if err != nil { +// t.Error(err) +// return +// } +// want := []*Build{} +// err = json.Unmarshal(in, &want) +// if err != nil { +// t.Error(err) +// return +// } +// if diff := cmp.Diff(got, want); diff != "" { +// t.Errorf("Unexpected response") +// t.Log(diff) +// } +// } + +func TestBuildRestart(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(mockHandler)) + defer ts.Close() + + client := New(ts.URL) + got, err := client.BuildRestart("octocat", "hello-world", 99, nil) + if err != nil { + t.Error(err) + return + } + + in, err := ioutil.ReadFile("testdata/build.json.golden") + if err != nil { + t.Error(err) + return + } + want := new(Build) + err = json.Unmarshal(in, want) + if err != nil { + t.Error(err) + return + } + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("Unexpected response") + t.Log(diff) + } +} + +func TestBuildCancel(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(mockHandler)) + defer ts.Close() + + client := New(ts.URL) + err := client.BuildCancel("octocat", "hello-world", 1) + if err != nil { + t.Error(err) + } +} + +func TestApprove(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(mockHandler)) + defer ts.Close() + + client := New(ts.URL) + err := client.Approve("octocat", "hello-world", 1, 2) + if err != nil { + t.Error(err) + } +} + +func TestDecline(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(mockHandler)) + defer ts.Close() + + client := New(ts.URL) + err := client.Decline("octocat", "hello-world", 1, 3) + if err != nil { + t.Error(err) + } +} + +// +// logs +// + +func TestLogs(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(mockHandler)) + defer ts.Close() + + client := New(ts.URL) + got, err := client.Logs("octocat", "hello-world", 1, 2, 3) + if err != nil { + t.Error(err) + return + } + + in, err := ioutil.ReadFile("testdata/logs.json.golden") + if err != nil { + t.Error(err) + return + } + want := []*Line{} + err = json.Unmarshal(in, &want) + if err != nil { + t.Error(err) + return + } + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("Unexpected response") + t.Log(diff) + } +} + +func TestLogsPurge(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(mockHandler)) + defer ts.Close() + + client := New(ts.URL) + err := client.LogsPurge("octocat", "hello-world", 1, 2, 3) + if err != nil { + t.Error(err) + return + } +} + +// +// mock server and testdata. +// + +func mockHandler(w http.ResponseWriter, r *http.Request) { + routes := []struct { + verb string + path string + body string + code int + }{ + // + // users + // + { + verb: "GET", + path: "/api/user", + body: "testdata/user.json", + code: 200, + }, + { + verb: "GET", + path: "/api/users/octocat", + body: "testdata/user.json", + code: 200, + }, + { + verb: "DELETE", + path: "/api/users/octocat", + code: 204, + }, + { + verb: "POST", + path: "/api/users", + body: "testdata/user.json", + code: 200, + }, + { + verb: "PATCH", + path: "/api/users/octocat", + body: "testdata/user.json", + code: 200, + }, + { + verb: "GET", + path: "/api/users", + body: "testdata/users.json", + code: 200, + }, + // + // repos + // + { + verb: "GET", + path: "/api/repos/octocat/hello-world", + body: "testdata/repo.json", + code: 200, + }, + { + verb: "GET", + path: "/api/user/repos", + body: "testdata/repos.json", + code: 200, + }, + { + verb: "POST", + path: "/api/user/repos", + body: "testdata/repos.json", + code: 200, + }, + { + verb: "POST", + path: "/api/repos/octocat/hello-world/repair", + code: 204, + }, + { + verb: "POST", + path: "/api/repos/octocat/hello-world/chown", + body: "testdata/repo.json", + code: 200, + }, + { + verb: "PATCH", + path: "/api/repos/octocat/hello-world", + body: "testdata/repo.json", + code: 200, + }, + { + verb: "POST", + path: "/api/repos/octocat/hello-world", + body: "testdata/repo.json", + code: 200, + }, + { + verb: "DELETE", + path: "/api/repos/octocat/hello-world", + code: 204, + }, + // + // crons + // + { + verb: "GET", + path: "/api/repos/octocat/hello-world/cron/nightly", + body: "testdata/cron.json", + code: 200, + }, + { + verb: "GET", + path: "/api/repos/octocat/hello-world/cron", + body: "testdata/crons.json", + code: 200, + }, + { + verb: "POST", + path: "/api/repos/octocat/hello-world/cron/nightly", + code: 204, + }, + { + verb: "DELETE", + path: "/api/repos/octocat/hello-world/cron/nightly", + code: 204, + }, + // + // builds + // + { + verb: "GET", + path: "/api/system/builds", + body: "testdata/builds.json", + code: 200, + }, + { + verb: "GET", + path: "/api/repos/octocat/hello-world/builds", + body: "testdata/builds.json", + code: 200, + }, + { + verb: "GET", + path: "/api/repos/octocat/hello-world/builds/1", + body: "testdata/build.json", + code: 200, + }, + { + verb: "GET", + path: "/api/repos/octocat/hello-world/builds/latest", + body: "testdata/build.json", + code: 200, + }, + { + verb: "POST", + path: "/api/repos/octocat/hello-world/builds/99", + body: "testdata/build.json", + code: 200, + }, + { + verb: "DELETE", + path: "/api/repos/octocat/hello-world/builds/1", + code: 204, + }, + { + verb: "POST", + path: "/api/repos/octocat/hello-world/builds/1/approve/2", + code: 204, + }, + { + verb: "POST", + path: "/api/repos/octocat/hello-world/builds/1/decline/3", + code: 204, + }, + // + // logs + // + { + verb: "GET", + path: "/api/repos/octocat/hello-world/builds/1/logs/2/3", + body: "testdata/logs.json", + code: 200, + }, + { + verb: "DELETE", + path: "/api/repos/octocat/hello-world/builds/1/logs/2/3", + code: 204, + }, + } + + path := r.URL.Path + verb := r.Method + for _, route := range routes { + if route.verb != verb { + continue + } + if route.path != path { + continue + } + if route.code == 204 { + w.WriteHeader(204) + return + } + body, err := ioutil.ReadFile(route.body) + if err != nil { + break + } + w.WriteHeader(route.code) + w.Write(body) + return + } + w.WriteHeader(404) +}