diff --git a/.github/workflows/check-go.yml b/.github/workflows/check-go.yml deleted file mode 100644 index da021d6d..00000000 --- a/.github/workflows/check-go.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: Check Go code - -on: - push: - paths: - - ".github/workflows/check-go.yml" - - "Taskfile.yml" - - "manager/**" - pull_request: - paths: - - ".github/workflows/check-go.yml" - - "Taskfile.yml" - - "manager/**" - # See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows#workflow_dispatch - workflow_dispatch: - # See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows#repository_dispatch - repository_dispatch: - -jobs: - check: - runs-on: ubuntu-latest - - steps: - - name: Checkout local repository - uses: actions/checkout@v2 - - - name: Install Go - uses: actions/setup-go@v2 - with: - go-version: "1.14" - - - name: Install Taskfile - uses: arduino/actions/setup-taskfile@master - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - version: 3.x - - - name: Lint - run: task go:lint - - - name: Run tests - run: task go:test - - - name: Check formatting - run: task go:check-formatting diff --git a/.github/workflows/manage-prs.yml b/.github/workflows/manage-prs.yml index 029c5e32..2ebd0de7 100644 --- a/.github/workflows/manage-prs.yml +++ b/.github/workflows/manage-prs.yml @@ -1,5 +1,8 @@ name: Manage PRs +env: + SUBMISSION_PARSER_VERSION: 1.0.0-rc2 # See: https://github.com/arduino/library-manager-submission-parser/releases + on: # pull_request_target trigger is used instead of pull_request so the token will have the write permissions needed to comment and merge. # Note that this means the version of the workflow from the PR base ref will be used as opposed to the head ref, as is the case with pull_request triggered workflows. @@ -25,7 +28,6 @@ jobs: contains(github.event.comment.body, 'ArduinoBot') ) runs-on: ubuntu-latest - steps: - name: Dummy step to make job valid run: "" @@ -77,27 +79,15 @@ jobs: index-entry: ${{ steps.parse-request.outputs.index-entry }} steps: - - name: Set environment variables - run: | - # See: https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable - echo "MANAGER_PATH=${{ runner.temp }}/manager" >> "$GITHUB_ENV" - - name: Checkout local repository uses: actions/checkout@v2 - - name: Install Go - uses: actions/setup-go@v2 - - - name: Install Taskfile - uses: arduino/actions/setup-taskfile@master + - name: Download submission parser + id: download-parser + uses: carlosperate/download-file-action@v1.0.3 with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - version: 3.x - - - name: Build manager - env: - GO_BUILD_FLAGS: -o $MANAGER_PATH - run: task go:build + file-url: https://github.com/arduino/library-registry-submission-parser/releases/download/${{ env.SUBMISSION_PARSER_VERSION }}/parser + location: ${{ runner.temp }} - name: Download diff uses: actions/download-artifact@v2 @@ -108,7 +98,8 @@ jobs: - name: Parse request id: parse-request run: | - REQUEST="$("$MANAGER_PATH" --diffpath="${{ needs.diff.outputs.path }}/${{ needs.diff.outputs.filename }}" --repopath="${{ github.workspace }}" --listname="repositories.txt")" + chmod u+x "${{ steps.download-parser.outputs.file-path }}" + REQUEST="$("${{ steps.download-parser.outputs.file-path }}" --diffpath="${{ needs.diff.outputs.path }}/${{ needs.diff.outputs.filename }}" --repopath="${{ github.workspace }}" --listname="repositories.txt")" # Due to limitations of the GitHub Actions workflow system, dedicated outputs must be created for use in certain workflow fields. echo "::set-output name=type::$(echo "$REQUEST" | jq -r -c '.type')" echo "::set-output name=submissions::$(echo "$REQUEST" | jq -c '.submissions')" diff --git a/.gitignore b/.gitignore deleted file mode 100644 index b573f51c..00000000 --- a/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -# Build artifacts -/manager/manager -/manager/manager.exe - -# Test artifacts -coverage_unit.txt - -# IDEs -.idea/ -.vscode/ -*.bak -*.code-workspace -*.sublime-workspace diff --git a/Taskfile.yml b/Taskfile.yml index 2d37769b..92489f2c 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -1,59 +1,9 @@ version: "3" vars: - DEFAULT_GO_PACKAGES: - sh: echo `cd manager && go list ./... | tr '\n' ' '` - DEFAULT_GO_PATHS: - sh: echo '`cd manager && go list -f '{{"{{"}}.Dir{{"}}"}}' ./...`' - PRETTIER: prettier@2.1.2 tasks: - go:build: - desc: Build the project - dir: manager - cmds: - - go build -v {{.GO_BUILD_FLAGS}} - - check: - desc: Test, lint, and check formatting of everything - deps: - - task: go:check - - task: docs:check - - task: config:check - - go:check: - desc: Test, lint, and check formatting of Go code - deps: - - task: go:lint - - task: go:test - - task: go:check-formatting - - go:lint: - desc: Lint Go code - dir: manager - cmds: - - go vet {{default .DEFAULT_GO_PACKAGES .GO_PACKAGES}} - - go get golang.org/x/lint/golint - - | - GOLINT_PATH="$(go list -f '{{"{{"}}.Target{{"}}"}}' golang.org/x/lint/golint || echo "false")" - "$GOLINT_PATH" {{default "-min_confidence 0.8 -set_exit_status" .GO_LINT_FLAGS}} "{{default .DEFAULT_GO_PACKAGES .GO_PACKAGES}}" - - go:test: - desc: Run unit tests - dir: manager - cmds: - - go test -v -short -run '{{default ".*" .GO_TEST_REGEX}}' {{default "-timeout 10m -coverpkg=./... -covermode=atomic" .GO_TEST_FLAGS}} -coverprofile=coverage_unit.txt {{default .DEFAULT_GO_PACKAGES .GO_PACKAGES}} - - go:check-formatting: - desc: Check Go code formatting - dir: manager - cmds: - - | - RESULTS="$(gofmt -l {{default .DEFAULT_GO_PATHS .GO_PATHS}})" - echo "$RESULTS" - test -z "$RESULTS" - docs:check: desc: Lint and check formatting of documentation files deps: @@ -119,18 +69,6 @@ tasks: cmds: - npx {{.PRETTIER}} --check "**/*.{yml,yaml,json}" - format: - desc: Format all files - deps: - - task: go:format - - task: docs:format - - task: config:format - - go:format: - desc: Format Go code - cmds: - - gofmt -l -w {{default .DEFAULT_GO_PATHS .GO_PATHS}} - docs:format: desc: Format documentation files cmds: diff --git a/manager/go.mod b/manager/go.mod deleted file mode 100644 index 970a19f0..00000000 --- a/manager/go.mod +++ /dev/null @@ -1,10 +0,0 @@ -module github.com/arduino/library-manager-list/manager - -go 1.14 - -require ( - github.com/arduino/go-paths-helper v1.5.0 - github.com/arduino/go-properties-orderedmap v1.4.0 - github.com/sourcegraph/go-diff v0.6.1 - github.com/stretchr/testify v1.3.0 -) diff --git a/manager/go.sum b/manager/go.sum deleted file mode 100644 index 1869338c..00000000 --- a/manager/go.sum +++ /dev/null @@ -1,22 +0,0 @@ -github.com/arduino/go-paths-helper v1.0.1/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck= -github.com/arduino/go-paths-helper v1.5.0 h1:RVo189hD+GhUS1rQ3gixwK1nSbvVR8MGIGa7Gxv2bdM= -github.com/arduino/go-paths-helper v1.5.0/go.mod h1:V82BWgAAp4IbmlybxQdk9Bpkz8M4Qyx+RAFKaG9NuvU= -github.com/arduino/go-properties-orderedmap v1.4.0 h1:YEbbzPqm1gXWDM/Jaq8tlvmh09z2qeHPJTUw9/VA4Dk= -github.com/arduino/go-properties-orderedmap v1.4.0/go.mod h1:DKjD2VXY/NZmlingh4lSFMEYCVubfeArCsGPGDwb2yk= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= -github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= -github.com/sourcegraph/go-diff v0.6.1 h1:hmA1LzxW0n1c3Q4YbrFgg4P99GSnebYa3x8gr0HZqLQ= -github.com/sourcegraph/go-diff v0.6.1/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/manager/main.go b/manager/main.go deleted file mode 100644 index 8238964f..00000000 --- a/manager/main.go +++ /dev/null @@ -1,386 +0,0 @@ -// Copyright 2020 ARDUINO SA (http://www.arduino.cc/) -// -// This software is released under the GNU General Public License version 3. -// The terms of this license can be found at: -// https://www.gnu.org/licenses/gpl-3.0.en.html -// -// You can be released from the requirements of the above licenses by purchasing -// a commercial license. Buying such a license is mandatory if you want to -// modify or otherwise use the software for commercial activities involving the -// Arduino software without disclosing the source code of your own applications. -// To purchase a commercial license, send an email to license@arduino.cc. - -package main - -import ( - "bytes" - "encoding/json" - "flag" - "fmt" - "io" - "net/http" - "net/url" - "os" - "os/exec" - "strings" - - "github.com/sourcegraph/go-diff/diff" - - "github.com/arduino/go-paths-helper" - properties "github.com/arduino/go-properties-orderedmap" -) - -// Git hosts that are supported for library repositories. -var supportedHosts []string = []string{ - "bitbucket.org", - "github.com", - "gitlab.com", -} - -// Libraries under these organizations will have the "Arduino" type and be linted with Arduino Lint in the "official" setting. -var officialOrganizations []string = []string{ - "github.com/arduino", - "github.com/arduino-libraries", - "github.com/bcmi-labs", - "github.com/vidor-libraries", -} - -// Libraries under these organizations will have the "Partner" type. -var partnerOrganizations []string = []string{ - "github.com/Azure", - "github.com/ms-iot", - "github.com/ameltech", -} - -// Libraries under these organizations will have the "Recommended" type. -var recommendedOrganizations []string = []string{ - "github.com/adafruit", -} - -// requestType is the type of the request data. -type requestType struct { - Type string `json:"type"` // Request type. - Submissions []submissionType `json:"submissions"` // Data for submitted libraries. - IndexEntry string `json:"indexEntry"` // Entry that will be made to the Library Manager index source file when the submission is accepted. -} - -// submissionType is the type of the data for each individual library submitted in the request. -type submissionType struct { - SubmissionURL string `json:"submissionURL"` // Library repository URL as submitted by user. Used to identify the submission to the user. - NormalizedURL string `json:"normalizedURL"` // Submission URL in the standardized format that will be used in the index entry. - Name string `json:"name"` // Library name. - Official bool `json:"official"` // Whether the library is official. - Tag string `json:"tag"` // Name of the submission repository's latest tag, which is used as the basis for the index entry and validation. - Error string `json:"error"` // Error message. -} - -// Command line flags. -var diffPathArgument = flag.String("diffpath", "", "") -var repoPathArgument = flag.String("repopath", "", "") -var listNameArgument = flag.String("listname", "", "") - -func main() { - // Validate flag input. - flag.Parse() - - if *diffPathArgument == "" { - errorExit("--diffpath flag is required") - } - - if *repoPathArgument == "" { - errorExit("--repopath flag is required") - } - - if *listNameArgument == "" { - errorExit("--listname flag is required") - } - - diffPath := paths.New(*diffPathArgument) - exist, err := diffPath.ExistCheck() - if !exist { - errorExit("diff file not found") - } - - listPath := paths.New(*repoPathArgument, *listNameArgument) - exist, err = listPath.ExistCheck() - if !exist { - errorExit(fmt.Sprintf("list file %s not found", listPath)) - } - - // Parse the PR diff. - rawDiff, err := diffPath.ReadFile() - if err != nil { - panic(err) - } - var request requestType - var submissionURLs []string - request.Type, submissionURLs = parseDiff(rawDiff, *listNameArgument) - - // Process the submissions. - var indexEntries []string - for _, submissionURL := range submissionURLs { - submission, indexEntry := populateSubmission(submissionURL, listPath) - request.Submissions = append(request.Submissions, submission) - indexEntries = append(indexEntries, indexEntry) - } - - // Assemble the index entry for the submissions. - request.IndexEntry = strings.Join(indexEntries, "%0A") - - // Marshal the request data into a JSON document. - var marshalledRequest bytes.Buffer - jsonEncoder := json.NewEncoder(io.Writer(&marshalledRequest)) - // By default, the json package HTML-sanitizes strings during marshalling (https://golang.org/pkg/encoding/json/#Marshal) - // It's not possible to change this behavior when using the simple json.MarshalIndent() approach. - jsonEncoder.SetEscapeHTML(false) - jsonEncoder.SetIndent("", "") // Single line. - err = jsonEncoder.Encode(request) - if err != nil { - panic(err) - } - - fmt.Println(marshalledRequest.String()) -} - -// errorExit prints the error message in a standardized format and exits with status 1. -func errorExit(message string) { - fmt.Printf("ERROR: %s\n", message) - os.Exit(1) -} - -// parseDiff parses the request diff and returns the request type and list of submission URLs. -func parseDiff(rawDiff []byte, listName string) (string, []string) { - var submissionURLs []string - - diffs, err := diff.ParseMultiFileDiff(rawDiff) - if err != nil { - panic(err) - } - - if (len(diffs) != 1) || (diffs[0].OrigName[2:] != listName) || (diffs[0].OrigName[2:] != diffs[0].NewName[2:]) { // Git diffs have a a/ or b/ prefix on file names. - // This is not a Library Manager submission. - return "other", nil - } - - var addedCount int - var deletedCount int - // Get the added URLs from the diff - for _, hunk := range diffs[0].Hunks { - hunkBody := string(hunk.Body) - for _, rawDiffLine := range strings.Split(hunkBody, "\n") { - diffLine := strings.TrimRight(rawDiffLine, " \t") - if len(diffLine) < 2 { - continue // Ignore blank lines. - } - - switch diffLine[0] { - case '+': - addedCount++ - submissionURLs = append(submissionURLs, strings.TrimSpace(diffLine[1:])) - case '-': - deletedCount++ - default: - continue - } - } - } - - var requestType string - if addedCount > 0 && deletedCount == 0 { - requestType = "submission" - } else if addedCount == 0 && deletedCount > 0 { - requestType = "removal" - } else { - requestType = "modification" - } - - return requestType, submissionURLs -} - -// populateSubmission does the checks on the submission that aren't provided by Arduino Lint and gathers the necessary data on it. -func populateSubmission(submissionURL string, listPath *paths.Path) (submissionType, string) { - indexSourceSeparator := "|" - var submission submissionType - - submission.SubmissionURL = submissionURL - - // Normalize and validate submission URL. - submissionURLObject, err := url.Parse(submission.SubmissionURL) - if err != nil { - submission.Error = fmt.Sprintf("Invalid submission URL (%s)", err) - return submission, "" - } - - // Check if URL is accessible. - httpResponse, err := http.Get(submissionURLObject.String()) - if err != nil { - submission.Error = fmt.Sprintf("Unable to load submission URL: %s", err) - return submission, "" - } - if httpResponse.StatusCode != http.StatusOK { - submission.Error = "Unable to load submission URL. Is the repository public?" - return submission, "" - } - - // Resolve redirects and normalize. - normalizedURLObject := normalizeURL(httpResponse.Request.URL) - - submission.NormalizedURL = normalizedURLObject.String() - - // Check if URL is from a supported Git host. - if !uRLIsUnder(normalizedURLObject, supportedHosts) { - submission.Error = normalizedURLObject.Host + " is not currently supported as a Git hosting website for Library Manager.%0ASee: https://github.com/arduino/Arduino/wiki/Library-Manager-FAQ#how-can-i-add-my-library-to-library-manager" - return submission, "" - } - - // Check if URL is a Git repository - err = exec.Command("git", "ls-remote", normalizedURLObject.String()).Run() - if err != nil { - if _, ok := err.(*exec.ExitError); ok { - submission.Error = "Submission URL is not a Git clone URL (e.g., https://github.com/arduino-libraries/Servo)." - return submission, "" - } - - panic(err) - } - - // Check if the URL is already in the index. - listLines, err := listPath.ReadFileAsLines() - occurrences := 0 - for _, listURL := range listLines { - listURLObject, err := url.Parse(strings.TrimSpace(listURL)) - if err != nil { - panic(err) // All list items have already passed parsing so something is broken if this happens. - } - - normalizedListURLObject := normalizeURL(listURLObject) - if normalizedListURLObject.String() == normalizedURLObject.String() { - occurrences++ - if occurrences > 1 { - submission.Error = "Submission URL is already in the Library Manager index." - return submission, "" - } - } - } - - // Determine the library types attributes. - submission.Official = uRLIsUnder(normalizedURLObject, officialOrganizations) - var types []string - if submission.Official { - types = append(types, "Arduino") - } - if uRLIsUnder(normalizedURLObject, partnerOrganizations) { - types = append(types, "Partner") - } - if uRLIsUnder(normalizedURLObject, recommendedOrganizations) { - types = append(types, "Recommended") - } - if types == nil { - types = append(types, "Contributed") - } - - submissionClonePath, err := paths.MkTempDir("", "") - if err != nil { - panic(err) - } - - err = exec.Command("git", "clone", "--depth", "1", normalizedURLObject.String(), submissionClonePath.String()).Run() - if err != nil { - panic(err) - } - - // Determine latest tag name in submission repo - err = os.Chdir(submissionClonePath.String()) - if err != nil { - panic(err) - } - err = exec.Command("git", "fetch", "--tags").Run() - if err != nil { - panic(err) - } - tagList, err := exec.Command("git", "rev-list", "--tags", "--max-count=1").Output() - if err != nil { - panic(err) - } - if string(tagList) == "" { - submission.Error = "The repository has no tags. You need to create a [release](https://docs.github.com/en/github/administering-a-repository/managing-releases-in-a-repository) or [tag](https://git-scm.com/docs/git-tag) that matches the `version` value in the library's library.properties file." - return submission, "" - } - latestTag, err := exec.Command("git", "describe", "--tags", strings.TrimSpace(string(tagList))).Output() - if err != nil { - panic(err) - } - submission.Tag = strings.TrimSpace(string(latestTag)) - - // Checkout latest tag. - err = exec.Command("git", "checkout", submission.Tag).Run() - if err != nil { - panic(err) - } - - // Get submission library name. It is necessary to record this in the index source entry because the library is locked to this name. - libraryPropertiesPath := submissionClonePath.Join("library.properties") - if !libraryPropertiesPath.Exist() { - submission.Error = "Library is missing a library.properties metadata file." - return submission, "" - } - libraryProperties, err := properties.LoadFromPath(libraryPropertiesPath) - if err != nil { - submission.Error = fmt.Sprintf("Invalid library.properties file (%s)", err) - return submission, "" - } - var ok bool - submission.Name, ok = libraryProperties.GetOk("name") - if !ok { - submission.Error = "library.properties is missing a name field" - return submission, "" - } - - // Assemble Library Manager index source entry string - indexEntry := strings.Join( - []string{ - submission.NormalizedURL, - strings.Join(types, ","), - submission.Name, - }, - indexSourceSeparator, - ) - - return submission, indexEntry -} - -// normalizeURL converts the URL into the standardized format used in the index. -func normalizeURL(rawURL *url.URL) url.URL { - normalizedPath := strings.TrimRight(rawURL.Path, "/") - if !strings.HasSuffix(normalizedPath, ".git") { - normalizedPath += ".git" - } - - return url.URL{ - Scheme: "https", - Host: rawURL.Host, - Path: normalizedPath, - } -} - -func uRLIsUnder(childURL url.URL, parentCandidates []string) bool { - for _, parentCandidate := range parentCandidates { - if !strings.HasSuffix(parentCandidate, "/") { - parentCandidate += "/" - } - parentCandidateURL, err := url.Parse("https://" + parentCandidate) - if err != nil { - panic(err) - } - - isUnderPath, err := paths.New(childURL.Path).IsInsideDir(paths.New(parentCandidateURL.Path)) - if err != nil { - panic(err) - } - if (childURL.Host == parentCandidateURL.Host) && isUnderPath { - return true - } - } - - return false -} diff --git a/manager/main_test.go b/manager/main_test.go deleted file mode 100644 index 14228996..00000000 --- a/manager/main_test.go +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright 2020 ARDUINO SA (http://www.arduino.cc/) -// -// This software is released under the GNU General Public License version 3. -// The terms of this license can be found at: -// https://www.gnu.org/licenses/gpl-3.0.en.html -// -// You can be released from the requirements of the above licenses by purchasing -// a commercial license. Buying such a license is mandatory if you want to -// modify or otherwise use the software for commercial activities involving the -// Arduino software without disclosing the source code of your own applications. -// To purchase a commercial license, send an email to license@arduino.cc. - -package main - -import ( - "net/url" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func Test_parseDiff(t *testing.T) { - testName := "Multiple files" - diff := []byte(` -diff --git a/README.md b/README.md -index d4edde0..807b76d 100644 ---- a/README.md -+++ b/README.md -@@ -1,0 +2 @@ -+hello -diff --git a/repositories.txt b/repositories.txt -index cff484d..e14c179 100644 ---- a/repositories.txt -+++ b/repositories.txt -@@ -8,0 +9 @@ https://github.com/arduino-libraries/Ethernet -+https://github.com/foo/bar -`) - - requestType, submissionURLs := parseDiff(diff, "repositories.txt") - assert.Equal(t, "other", requestType, testName) - assert.Nil(t, submissionURLs, testName) - - testName = "Not list" - diff = []byte(` -diff --git a/README.md b/README.md -index d4edde0..807b76d 100644 ---- a/README.md -+++ b/README.md -@@ -1 +1,2 @@ - # Arduino Library Manager list -+hello -`) - - requestType, submissionURLs = parseDiff(diff, "repositories.txt") - assert.Equal(t, "other", requestType, testName) - assert.Nil(t, submissionURLs, testName) - - testName = "List filename change" - diff = []byte(` -diff --git a/repositories.txt b/foobar.txt -similarity index 99% -rename from repositories.txt -rename to foobar.txt -index cff484d..e14c179 100644 ---- a/repositories.txt -+++ b/foobar.txt -@@ -8,0 +9 @@ https://github.com/arduino-libraries/Ethernet -+https://github.com/foo/bar -`) - - requestType, submissionURLs = parseDiff(diff, "repositories.txt") - assert.Equal(t, "other", requestType, testName) - assert.Nil(t, submissionURLs, testName) - - testName = "Submission" - diff = []byte(` -diff --git a/repositories.txt b/repositories.txt -index cff484d..9f67763 100644 ---- a/repositories.txt -+++ b/repositories.txt -@@ -8,0 +9,2 @@ https://github.com/arduino-libraries/Ethernet -+https://github.com/foo/bar -+https://github.com/foo/baz -`) - - requestType, submissionURLs = parseDiff(diff, "repositories.txt") - assert.Equal(t, "submission", requestType, testName) - assert.ElementsMatch(t, submissionURLs, []string{"https://github.com/foo/bar", "https://github.com/foo/baz"}, testName) - - testName = "Submission w/ no newline at end of file" - diff = []byte(` -diff --git a/repositories.txt b/repositories.txt -index cff484d..1b0b80b 100644 ---- a/repositories.txt -+++ b/repositories.txt -@@ -3391,0 +3392 @@ https://github.com/lbernstone/plotutils -+https://github.com/foo/bar -\ No newline at end of file -`) - - requestType, submissionURLs = parseDiff(diff, "repositories.txt") - assert.Equal(t, "submission", requestType, testName) - assert.ElementsMatch(t, submissionURLs, []string{"https://github.com/foo/bar"}, testName) - - testName = "Submission w/ blank line" - diff = []byte(` -diff --git a/repositories.txt b/repositories.txt -index cff484d..1b0b80b 100644 ---- a/repositories.txt -+++ b/repositories.txt -@@ -3391,0 +3392 @@ https://github.com/lbernstone/plotutils -+https://github.com/foo/bar -\ No newline at end of file -`) - - requestType, submissionURLs = parseDiff(diff, "repositories.txt") - assert.Equal(t, "submission", requestType, testName) - assert.ElementsMatch(t, submissionURLs, []string{"https://github.com/foo/bar"}, testName) - - testName = "Removal" - diff = []byte(` -diff --git a/repositories.txt b/repositories.txt -index cff484d..38e11d8 100644 ---- a/repositories.txt -+++ b/repositories.txt -@@ -8 +7,0 @@ https://github.com/firmata/arduino --https://github.com/arduino-libraries/Ethernet -`) - - requestType, submissionURLs = parseDiff(diff, "repositories.txt") - assert.Equal(t, "removal", requestType, testName) - assert.Nil(t, submissionURLs, testName) - - testName = "Modification" - diff = []byte(` -diff --git a/repositories.txt b/repositories.txt -index cff484d..8b401a1 100644 ---- a/repositories.txt -+++ b/repositories.txt -@@ -8 +8 @@ https://github.com/firmata/arduino --https://github.com/arduino-libraries/Ethernet -+https://github.com/foo/bar -`) - - requestType, submissionURLs = parseDiff(diff, "repositories.txt") - assert.Equal(t, "modification", requestType, testName) - assert.Equal(t, submissionURLs, []string{"https://github.com/foo/bar"}, testName) -} - -func Test_normalizeURL(t *testing.T) { - testTables := []struct { - testName string - rawURL string - expectedNormalizedURL string - }{ - {"Trailing slash", "https://github.com/foo/bar/", "https://github.com/foo/bar.git"}, - {".git suffix", "https://github.com/foo/bar.git", "https://github.com/foo/bar.git"}, - {"http://", "http://github.com/foo/bar", "https://github.com/foo/bar.git"}, - {"git://", "git://github.com/foo/bar", "https://github.com/foo/bar.git"}, - } - - for _, testTable := range testTables { - rawURL, err := url.Parse(testTable.rawURL) - require.Nil(t, err) - expectedNormalizedURL, err := url.Parse(testTable.expectedNormalizedURL) - require.Nil(t, err) - - assert.Equal(t, *expectedNormalizedURL, normalizeURL(rawURL), testTable.testName) - } -} - -func Test_uRLIsUnder(t *testing.T) { - testTables := []struct { - testName string - childURL string - parentCandidates []string - assertion assert.BoolAssertionFunc - }{ - {"Match, root path", "https://github.com/foo/bar", []string{"example.com", "github.com"}, assert.True}, - {"Mismatch, root path", "https://github.com/foo/bar", []string{"example.com", "example.org"}, assert.False}, - {"Match, subfolder", "https://github.com/foo/bar", []string{"example.com/foo", "github.com/foo"}, assert.True}, - {"Mismatch, subfolder", "https://github.com/foo/bar", []string{"example.com/foo", "github.org/bar"}, assert.False}, - } - - for _, testTable := range testTables { - childURL, err := url.Parse(testTable.childURL) - require.Nil(t, err) - - t.Run(testTable.testName, func(t *testing.T) { - testTable.assertion(t, uRLIsUnder(*childURL, testTable.parentCandidates)) - }) - } -}