diff --git a/.gitignore b/.gitignore index 847e63a4..0d458523 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ /cmd/upm/upm /dist/ -/internal/statik/ /packaging/aur/*.tar.gz /packaging/aur/*.tar.xz /packaging/aur/pkg/ diff --git a/.semaphore/install_nix.sh b/.semaphore/install_nix.sh new file mode 100755 index 00000000..1dbf27b2 --- /dev/null +++ b/.semaphore/install_nix.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env sh + +set -e +set -o xtrace + +install_script_cache_key="install-nix-2.16.1" +install_script="/tmp/$install_script_cache_key" + +# Install Nix. Install the base version if the currently installed version is +# not what we expect. +if [ \ + ! -f ~/.nix-profile/etc/profile.d/nix.sh -o \ + "$(~/.nix-profile/bin/nix --version)" != "nix (Nix) 2.16.1" \ + ]; then + curl -L https://releases.nixos.org/nix/nix-2.16.1/install | sh +fi + +source ~/.nix-profile/etc/profile.d/nix.sh + +set +e diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml index fc6a9b04..b6e4a6f4 100644 --- a/.semaphore/semaphore.yml +++ b/.semaphore/semaphore.yml @@ -3,121 +3,49 @@ version: v1.0 name: UPM agent: machine: - type: e1-standard-2 - os_image: ubuntu2004 + type: s1-goval global_job_config: + env_vars: + - name: UPM_CI + value: "1" prologue: commands: - checkout - git switch --detach - export PATH="/tmp:$PATH" + - source ./.semaphore/install_nix.sh blocks: - - name: setup - dependencies: [] - task: - jobs: - - name: cache nix installer - commands: - - >- - cache has_key nix-installer || ( - curl - --proto '=https' - -o /tmp/nix-installer - --tlsv1.2 -sSf -L - https://github.com/DeterminateSystems/nix-installer/releases/download/v0.7.0/nix-installer-x86_64-linux - && chmod +x /tmp/nix-installer - && cache store nix-installer /tmp/nix-installer - ) - # TODO: cache the dependencies of the nix package - # this might be possible based on exploratory: - # ``` - # $ DERIVATION="$(nix path-info --derivation .)" - # $ declare -p DERIVATION - # $ nix-store --query --references "${DERIVATION}" - # ``` - # The above spits out the deps of the derivation - # just have to filter out the `-source` - - name: cache golint-ci - commands: - - >- - cache has_key golangci-lint || ( - mkdir -p bin && - curl - -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh - | sh -s -- -b /tmp v1.52.2 - && cache store golangci-lint /tmp/golangci-lint - ) - - name: build - dependencies: [ setup ] + dependencies: [ ] task: - prologue: - commands: - - cache restore nix-installer - - sudo /tmp/nix-installer install linux --no-confirm --logger compact - - . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh jobs: - name: nix commands: - - cache restore nix-store - nix build - ./result/bin/upm --version - - cache store nix-store /nix - # TODO: get this working again - # - name: docker - # commands: - # - make light full - - - name: lint - dependencies: [ setup ] + + - name: test + dependencies: [ build ] task: jobs: - - name: golang lint + - name: test-suite commands: - - make generated - - cache restore golangci-lint - - golangci-lint run + - nix develop -c nix shell -c make test-suite + epilogue: + commands: + - test-results publish junit.xml - - name: release - dependencies: [ build, lint ] - run: - # git tag can be eg 'v1.0', 'v1.0.1', 'v1.0-alpha', or 'v1.0-alpha-4' - when: "tag =~ 'v[0-9]+\.[0-9]+(\.[0-9]+)?(-.*)*'" + - name: lint + dependencies: [ ] task: - prologue: - commands: - - cache restore dist jobs: - - name: goreleaser + - name: golang lint commands: + - make generated + - cache restore golangci-lint - >- - cache has_key goreleaser || ( - curl - --proto '=https' - -o /tmp/goreleaser - -sL https://git.io/goreleaser - && chmod +x /tmp/goreleaser - && cache store goreleaser /tmp/goreleaser - ) - - cache restore goreleaser - - goreleaser --release-notes=<(scripts/extract-changelog.py) - # - name: snapcraft - # commands: - # - >- - # snapcraft login --with <( - # echo "$SNAPCRAFT_LOGIN_FILE" | - # base64 --decode --ignore-garbage - # ) - # - scripts/upload-snap.bash - # - name: deploy - # commands: - # - >- - # echo "$DOCKER_PASSWORD" | - # docker login - # --username "$DOCKER_USERNAME" - # --password-stdin - # - make deploy - # - | - # git tag "semaphoreci_pipeline_" + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh + | sh -s -- -b /tmp v1.52.2 + - /tmp/golangci-lint run diff --git a/Makefile b/Makefile index 6adac2eb..73744105 100644 --- a/Makefile +++ b/Makefile @@ -80,3 +80,13 @@ help: ## Show this message .PHONY: test test: go test ./... -v + +.PHONY: test-suite +ifdef UPM_CI +test-suite: + go get gotest.tools/gotestsum + go run gotest.tools/gotestsum --junitfile ./junit.xml ./test-suite +else +test-suite: + nix develop -c nix shell -c go test ./test-suite +endif diff --git a/internal/backends/backends.go b/internal/backends/backends.go index 31b50f1b..566e6918 100644 --- a/internal/backends/backends.go +++ b/internal/backends/backends.go @@ -29,7 +29,7 @@ var languageBackends = []api.LanguageBackend{ python.Python2Backend, nodejs.BunBackend, nodejs.NodejsNPMBackend, - nodejs.NodejsPNPMBackend, + nodejs.NodejsPNPMBackend, nodejs.NodejsYarnBackend, ruby.RubyBackend, elisp.ElispBackend, diff --git a/nix/devshell/default.nix b/nix/devshell/default.nix index a4460939..a5437077 100644 --- a/nix/devshell/default.nix +++ b/nix/devshell/default.nix @@ -1,10 +1,17 @@ { + bun, go, mkShell, + nodejs, + nodePackages, }: mkShell { name = "upm"; packages = [ + bun go + nodejs + nodePackages.pnpm + nodePackages.yarn ]; } diff --git a/nix/upm/default.nix b/nix/upm/default.nix index 2e38ad1c..7cb2c184 100644 --- a/nix/upm/default.nix +++ b/nix/upm/default.nix @@ -3,11 +3,24 @@ rev, makeWrapper, }: -buildGoModule { +buildGoModule rec { pname = "upm"; version = rev; - src = ../../.; + src = builtins.path { + name = "${pname}-${version}-src"; + path = ../../.; + filter = path: _: builtins.all (block: (builtins.baseNameOf path) != block) [ + ".github" + ".semaphore" + "packaging" + "scripts" + "test-suite" + ".goreleaser.yml" + ".replit" + "replit.nix" + ]; + }; vendorHash = "sha256-2F2/BcHUEpbYxmAW1SsIBbn6U2VWinWjdxMvsbzfKsc="; diff --git a/test-suite/Add_test.go b/test-suite/Add_test.go new file mode 100644 index 00000000..930d5368 --- /dev/null +++ b/test-suite/Add_test.go @@ -0,0 +1,65 @@ +package testSuite + +import ( + "testing" + + testUtils "github.com/replit/upm/test-suite/utils" +) + +func TestAdd(t *testing.T) { + for _, bt := range languageBackends { + bt.Start(t) + + var pkgs []string + switch bt.Backend.Name { + default: + t.Run(bt.Backend.Name, func(t *testing.T) { + t.Skip("no test") + }) + continue + } + + bt.Subtest(bt.Backend.Name, func(bt testUtils.BackendT) { + doAdd(bt, pkgs...) + }) + } +} + +func doAdd(bt testUtils.BackendT, pkgs ...string) { + for _, tmpl := range standardTemplates { + template := bt.Backend.Name + "/" + tmpl + "/" + bt.Subtest(tmpl, func(bt testUtils.BackendT) { + if tmpl != "no-deps" { + bt.Subtest("locked", func(bt testUtils.BackendT) { + bt.Subtest("each", func(bt testUtils.BackendT) { + bt.AddTestFile(template+bt.Backend.Specfile, bt.Backend.Specfile) + bt.AddTestFile(template+bt.Backend.Lockfile, bt.Backend.Lockfile) + for _, pkg := range pkgs { + bt.UpmAdd(pkg) + } + }) + + bt.Subtest("all", func(bt testUtils.BackendT) { + bt.AddTestFile(template+bt.Backend.Specfile, bt.Backend.Specfile) + bt.AddTestFile(template+bt.Backend.Lockfile, bt.Backend.Lockfile) + bt.UpmAdd(pkgs...) + }) + }) + } + + bt.Subtest("unlocked", func(bt testUtils.BackendT) { + bt.Subtest("each", func(bt testUtils.BackendT) { + bt.AddTestFile(template+bt.Backend.Specfile, bt.Backend.Specfile) + for _, pkg := range pkgs { + bt.UpmAdd(pkg) + } + }) + + bt.Subtest("all", func(bt testUtils.BackendT) { + bt.AddTestFile(template+bt.Backend.Specfile, bt.Backend.Specfile) + bt.UpmAdd(pkgs...) + }) + }) + }) + } +} diff --git a/test-suite/Guess_test.go b/test-suite/Guess_test.go new file mode 100644 index 00000000..e267c9b5 --- /dev/null +++ b/test-suite/Guess_test.go @@ -0,0 +1,67 @@ +package testSuite + +import ( + "io" + "strings" + "testing" + + "github.com/replit/upm/test-suite/templates" + testUtils "github.com/replit/upm/test-suite/utils" +) + +func TestGuess(t *testing.T) { + for _, bt := range languageBackends { + bt.Start(t) + + tests := make(map[string]map[string][]string) + switch bt.Backend.Name { + default: + t.Run(bt.Backend.Name, func(t *testing.T) { + t.Skip("no test") + }) + continue + } + + for ext, guessSuites := range tests { + bt.Subtest(ext, func(bt testUtils.BackendT) { + for template, tests := range guessSuites { + bt.Subtest(template, func(bt testUtils.BackendT) { + for _, test := range tests { + testSrcFile := "guess/" + template + "/" + test + + bt.Subtest(test, func(bt testUtils.BackendT) { + bt.AddTestFile(testSrcFile, test+"."+ext) + bt.AddTestFile(bt.Backend.Name+"/no-deps/"+bt.Backend.Specfile, bt.Backend.Specfile) + + expectFile, err := templates.FS.Open(testSrcFile + ".expect") + if err != nil { + bt.Fail("No expect file found for %s: %v", testSrcFile, err) + } + + var expectsText strings.Builder + _, err = io.Copy(&expectsText, expectFile) + if err != nil { + bt.Fail("Failed to read expect file for %s: %v", testSrcFile, err) + } + + expects := strings.Split(strings.TrimSpace(expectsText.String()), "\n") + + bt.UpmGuess(expects...) + }) + } + }) + } + }) + } + } +} + +var js = []string{ + "basic", + "dedup", + "nested", +} + +var ts = []string{ + "typeImports", +} diff --git a/test-suite/Info_test.go b/test-suite/Info_test.go new file mode 100644 index 00000000..6db77dd0 --- /dev/null +++ b/test-suite/Info_test.go @@ -0,0 +1,31 @@ +package testSuite + +import ( + "testing" + + testUtils "github.com/replit/upm/test-suite/utils" +) + +func TestInfo(t *testing.T) { + for _, bt := range languageBackends { + bt.Start(t) + + switch bt.Backend.Name { + default: + t.Run(bt.Backend.Name, func(t *testing.T) { + t.Skip("no test") + }) + continue + } + } +} + +func doInfo(t testUtils.BackendT, pkgs ...string) { + t.Subtest(t.Backend.Name, func(t testUtils.BackendT) { + for _, pkg := range pkgs { + t.Subtest(pkg, func(t testUtils.BackendT) { + t.UpmInfo(pkg) + }) + } + }) +} diff --git a/test-suite/Install_test.go b/test-suite/Install_test.go new file mode 100644 index 00000000..a94d9530 --- /dev/null +++ b/test-suite/Install_test.go @@ -0,0 +1,47 @@ +package testSuite + +import ( + "os" + "path" + "testing" + + testUtils "github.com/replit/upm/test-suite/utils" +) + +func TestInstall(t *testing.T) { + for _, bt := range languageBackends { + bt.Start(t) + + if true { + t.Run(bt.Backend.Name, func(t *testing.T) { + t.Skip("no test") + }) + continue + } + + bt.Subtest(bt.Backend.Name, doInstall) + } +} + +func doInstall(bt testUtils.BackendT) { + for _, tmpl := range standardTemplates { + if tmpl == "no-deps" { + continue + } + + template := bt.Backend.Name + "/" + tmpl + "/" + bt.Subtest(tmpl, func(bt testUtils.BackendT) { + bt.AddTestFile(template+bt.Backend.Specfile, bt.Backend.Specfile) + bt.AddTestFile(template+bt.Backend.Lockfile, bt.Backend.Lockfile) + + bt.UpmInstall() + + packageDirPath := bt.UpmPackageDir() + + _, err := os.Stat(path.Join(bt.TestDir(), packageDirPath)) + if err != nil { + bt.Fail("expected package dir to exist: %v", err) + } + }) + } +} diff --git a/test-suite/List_test.go b/test-suite/List_test.go new file mode 100644 index 00000000..300a9d21 --- /dev/null +++ b/test-suite/List_test.go @@ -0,0 +1,75 @@ +package testSuite + +import ( + "testing" + + testUtils "github.com/replit/upm/test-suite/utils" +) + +func TestList(t *testing.T) { + for _, bt := range languageBackends { + bt.Start(t) + + var templatesToPackages map[string][]string + switch bt.Backend.Name { + default: + t.Run(bt.Backend.Name, func(t *testing.T) { + t.Skip("no test") + }) + continue + } + + bt.Subtest(bt.Backend.Name, func(bt testUtils.BackendT) { + doList(bt, templatesToPackages) + }) + } +} + +func doList(bt testUtils.BackendT, templatesToPackages map[string][]string) { + for tmpl, expectPkgs := range templatesToPackages { + template := bt.Backend.Name + "/" + tmpl + "/" + + bt.Subtest(tmpl, func(bt testUtils.BackendT) { + bt.AddTestFile(template+bt.Backend.Specfile, bt.Backend.Specfile) + if tmpl != "no-deps" { + bt.AddTestFile(template+bt.Backend.Lockfile, bt.Backend.Lockfile) + } + + specs := bt.UpmListSpecFile() + if len(specs) != len(expectPkgs) { + bt.Fail("Expected %v packages, got %v", len(expectPkgs), len(specs)) + } + + locks := bt.UpmListLockFile() + if len(locks) < len(expectPkgs) { + bt.Fail("Expected %v packages, got %v", len(expectPkgs), len(locks)) + } + + for _, pkg := range expectPkgs { + found := false + for _, spec := range specs { + if pkg == spec.Name { + found = true + break + } + } + + if !found { + bt.Fail("package %v not found in spec list", pkg) + } + + found = false + for _, lock := range locks { + if pkg == lock.Name { + found = true + break + } + } + + if !found { + bt.Fail("package %v not found in lock list", pkg) + } + } + }) + } +} diff --git a/test-suite/Lock_test.go b/test-suite/Lock_test.go new file mode 100644 index 00000000..0a180e6d --- /dev/null +++ b/test-suite/Lock_test.go @@ -0,0 +1,52 @@ +package testSuite + +import ( + "testing" + + testUtils "github.com/replit/upm/test-suite/utils" +) + +func TestLock(t *testing.T) { + for _, bt := range languageBackends { + bt.Start(t) + + if true { + t.Run(bt.Backend.Name, func(t *testing.T) { + t.Skip("no test") + }) + continue + } + + bt.Subtest(bt.Backend.Name, doLock) + } +} + +func doLock(bt testUtils.BackendT) { + for _, tmpl := range standardTemplates { + template := bt.Backend.Name + "/" + tmpl + "/" + + bt.Subtest(tmpl, func(bt testUtils.BackendT) { + bt.AddTestFile(template+bt.Backend.Specfile, bt.Backend.Specfile) + + specDeps := bt.UpmListSpecFile() + + bt.UpmLock() + + lockDeps := bt.UpmListLockFile() + + for _, specDep := range specDeps { + found := false + for _, lockDep := range lockDeps { + if specDep.Name == lockDep.Name { + found = true + break + } + } + + if !found { + bt.Fail("expected %s in lock file", specDep.Name) + } + } + }) + } +} diff --git a/test-suite/Main_test.go b/test-suite/Main_test.go new file mode 100644 index 00000000..4bf0f585 --- /dev/null +++ b/test-suite/Main_test.go @@ -0,0 +1,24 @@ +package testSuite + +import ( + "github.com/replit/upm/internal/backends" + "github.com/replit/upm/test-suite/templates" + testUtils "github.com/replit/upm/test-suite/utils" +) + +var languageBackends []testUtils.BackendT + +var standardTemplates = []string{ + "no-deps", + "one-dep", + "many-deps", +} + +func init() { + backends.SetupAll() + + for _, bn := range backends.GetBackendNames() { + bt := testUtils.InitBackendT(backends.GetBackend(bn), &templates.FS) + languageBackends = append(languageBackends, bt) + } +} diff --git a/test-suite/Remove_test.go b/test-suite/Remove_test.go new file mode 100644 index 00000000..c42cee21 --- /dev/null +++ b/test-suite/Remove_test.go @@ -0,0 +1,52 @@ +package testSuite + +import ( + "testing" + + testUtils "github.com/replit/upm/test-suite/utils" +) + +func TestRemove(t *testing.T) { + for _, bt := range languageBackends { + bt.Start(t) + + var pkgsToRemove map[string][]string + switch bt.Backend.Name { + default: + t.Run(bt.Backend.Name, func(t *testing.T) { + t.Skip("no test") + }) + continue + } + + bt.Subtest(bt.Backend.Name, func(bt testUtils.BackendT) { + doRemove(bt, pkgsToRemove) + }) + } +} + +func doRemove(bt testUtils.BackendT, templatesToPkgsToRemove map[string][]string) { + for tmpl, pkgsToRemove := range templatesToPkgsToRemove { + template := bt.Backend.Name + "/" + tmpl + "/" + + if tmpl != "no-deps" { + bt.Subtest(tmpl, func(bt testUtils.BackendT) { + bt.Subtest("locked", func(bt testUtils.BackendT) { + bt.Subtest("each", func(bt testUtils.BackendT) { + bt.AddTestFile(template+bt.Backend.Specfile, bt.Backend.Specfile) + bt.AddTestFile(template+bt.Backend.Lockfile, bt.Backend.Lockfile) + for _, pkg := range pkgsToRemove { + bt.UpmRemove(pkg) + } + }) + + bt.Subtest("all", func(bt testUtils.BackendT) { + bt.AddTestFile(template+bt.Backend.Specfile, bt.Backend.Specfile) + bt.AddTestFile(template+bt.Backend.Lockfile, bt.Backend.Lockfile) + bt.UpmRemove(pkgsToRemove...) + }) + }) + }) + } + } +} diff --git a/test-suite/Search_test.go b/test-suite/Search_test.go new file mode 100644 index 00000000..ba497cc6 --- /dev/null +++ b/test-suite/Search_test.go @@ -0,0 +1,36 @@ +package testSuite + +import ( + "testing" + + testUtils "github.com/replit/upm/test-suite/utils" +) + +func TestSearch(t *testing.T) { + for _, bt := range languageBackends { + bt.Start(t) + + switch bt.Backend.Name { + default: + t.Run(bt.Backend.Name, func(t *testing.T) { + t.Skip("no test") + }) + continue + } + } +} + +type searchTest struct { + query string + expected string +} + +func doSearch(t testUtils.BackendT, tests []searchTest) { + t.Subtest(t.Backend.Name, func(t testUtils.BackendT) { + for _, test := range tests { + t.Subtest(test.query, func(t testUtils.BackendT) { + t.UpmSearch(test.query, test.expected) + }) + } + }) +} diff --git a/test-suite/WhichLanguage_test.go b/test-suite/WhichLanguage_test.go new file mode 100644 index 00000000..d90e7416 --- /dev/null +++ b/test-suite/WhichLanguage_test.go @@ -0,0 +1,41 @@ +package testSuite + +import ( + "testing" + + testUtils "github.com/replit/upm/test-suite/utils" +) + +func TestWhichLanguage(t *testing.T) { + defaults := map[string]bool{ + "bun": true, + } + + for _, bt := range languageBackends { + bt.Start(t) + + if true { + t.Run(bt.Backend.Name, func(t *testing.T) { + t.Skip("no test") + }) + continue + } + + template := bt.Backend.Name + "/one-dep/" + + bt.Subtest(bt.Backend.Name, func(bt testUtils.BackendT) { + bt.Subtest("locked", func(bt testUtils.BackendT) { + bt.AddTestFile(template+bt.Backend.Specfile, bt.Backend.Specfile) + bt.AddTestFile(template+bt.Backend.Lockfile, bt.Backend.Lockfile) + bt.UpmWhichLanguage() + }) + + if defaults[bt.Backend.Name] { + bt.Subtest("default", func(bt testUtils.BackendT) { + bt.AddTestFile(template+bt.Backend.Specfile, bt.Backend.Specfile) + bt.UpmWhichLanguage() + }) + } + }) + } +} diff --git a/test-suite/templates/templates.go b/test-suite/templates/templates.go new file mode 100644 index 00000000..dec2588b --- /dev/null +++ b/test-suite/templates/templates.go @@ -0,0 +1,6 @@ +package templates + +import "embed" + +//go:embed * +var FS embed.FS diff --git a/test-suite/utils/backend_t.go b/test-suite/utils/backend_t.go new file mode 100644 index 00000000..c25e1228 --- /dev/null +++ b/test-suite/utils/backend_t.go @@ -0,0 +1,113 @@ +package testUtils + +import ( + "embed" + "io" + "os" + "os/exec" + "path" + "strings" + "testing" + + "github.com/replit/upm/internal/api" +) + +type BackendT struct { + Backend api.LanguageBackend + t *testing.T + templates *embed.FS + testDir string +} + +func (bt BackendT) assertT() { + if bt.t == nil { + panic("BackendT not started") + } +} + +func InitBackendT( + backend api.LanguageBackend, + templates *embed.FS, +) BackendT { + return BackendT{ + Backend: backend, + templates: templates, + } +} + +func (bt BackendT) Fail(fmt string, args ...interface{}) { + bt.t.Helper() + bt.t.Fatalf(fmt, args...) +} + +func (bt *BackendT) Start(t *testing.T) { + bt.t = t +} + +func (bt BackendT) Subtest(name string, test func(bt BackendT)) { + bt.assertT() + bt.t.Run(name, func(t *testing.T) { + inner := InitBackendT(bt.Backend, bt.templates) + inner.t = t + test(inner) + }) +} + +func (bt *BackendT) TestDir() string { + if bt.testDir == "" { + bt.assertT() + bt.testDir = bt.t.TempDir() + } + + return bt.testDir +} + +func (bt *BackendT) AddTestFile(template, as string) { + templates := *bt.templates + + f, err := templates.Open(template) + if err != nil { + bt.t.Fatalf("failed to get template %s: %v", template, err) + } + + dstPath := path.Join(bt.TestDir(), as) + dst, err := os.OpenFile(dstPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) + if err != nil { + bt.t.Fatalf("failed to open or create test file %s: %v", dstPath, err) + } + + _, err = io.Copy(dst, f) + if err != nil { + bt.t.Fatalf("failed to read template %s: %v", template, err) + } +} + +func (bt *BackendT) Exec(command string, args ...string) (struct{ Stdout, Stderr string }, error) { + bt.Log("$ %s %s", command, strings.Join(args, " ")) + + cmd := exec.Command(command, args...) + cmd.Dir = bt.TestDir() + + stdout := strings.Builder{} + cmd.Stdout = &stdout + + stderr := strings.Builder{} + cmd.Stderr = &stderr + + err := cmd.Run() + + Stdout := stdout.String() + Stderr := stderr.String() + + bt.Log("=== STDOUT ===\n%s", Stdout) + bt.Log("=== STDERR ===\n%s", Stderr) + + return struct{ Stdout, Stderr string }{ + Stdout, + Stderr, + }, err +} + +func (bt *BackendT) Log(format string, args ...interface{}) { + bt.t.Logf(format, args...) +} diff --git a/test-suite/utils/upm.go b/test-suite/utils/upm.go new file mode 100644 index 00000000..e5613870 --- /dev/null +++ b/test-suite/utils/upm.go @@ -0,0 +1,333 @@ +package testUtils + +import ( + "encoding/json" + "strings" + + "github.com/replit/upm/internal/api" +) + +func (bt *BackendT) UpmAdd(pkgs ...string) { + beforeLockDeps := bt.UpmListLockFile() + beforeSpecDeps := bt.UpmListSpecFile() + + args := []string{ + "--lang", + bt.Backend.Name, + "add", + } + _, err := bt.Exec( + "upm", + append(args, pkgs...)..., + ) + + if err != nil { + bt.Fail("upm failed to add: %v", err) + } + + afterLockDeps := bt.UpmListLockFile() + afterSpecDeps := bt.UpmListSpecFile() + + if len(beforeLockDeps) >= len(afterLockDeps) { + bt.Fail("expected more deps in lock file after add (before %d, after %d)", len(beforeLockDeps), len(afterLockDeps)) + } + if len(beforeSpecDeps) >= len(afterSpecDeps) { + bt.Fail("expected more deps in lock file after add (before %d, after %d)", len(beforeLockDeps), len(afterLockDeps)) + } + + for _, pkg := range pkgs { + found := false + for _, dep := range afterLockDeps { + if dep.Name == pkg { + found = true + break + } + } + if !found { + bt.Fail("expected %s in lock file after add", pkg) + } + + found = false + for _, dep := range afterSpecDeps { + if dep.Name == pkg { + found = true + break + } + } + if !found { + bt.Fail("expected %s in spec file after add", pkg) + } + } +} + +func (bt *BackendT) UpmGuess(expect ...string) { + out, err := bt.Exec( + "upm", + "--lang", + bt.Backend.Name, + "guess", + ) + + if err != nil { + bt.Fail("upm failed to guess: %v", err) + } + + guesses := strings.Split(strings.TrimSpace(out.Stdout), "\n") + if len(guesses) != len(expect) { + bt.Fail("expected %d guesses, got %d", len(expect), len(guesses)) + } + + for len(guesses) > 0 { + guess := guesses[0] + guesses = guesses[1:] + + found := false + for ii, expected := range expect { + if guess == expected { + found = true + expect = append(expect[:ii], expect[ii+1:]...) + break + } + } + + if !found { + bt.Fail("unexpected guess %s", guess) + } + } + + if len(expect) != 0 { + bt.Fail("expected guesses %v", expect) + } +} + +func (bt *BackendT) UpmInfo(pkg string) { + out, err := bt.Exec( + "upm", + "--lang", + bt.Backend.Name, + "info", + "--format", + "json", + pkg, + ) + + if err != nil { + bt.Fail("upm failed to get info: %v", err) + } + + var info api.PkgInfo + err = json.NewDecoder(strings.NewReader(out.Stdout)).Decode(&info) + if err != nil { + bt.Fail("failed to decode json: %v", err) + } + + if info.Name != pkg { + bt.Fail("expected info for %s, got %s", pkg, info.Name) + } +} + +func (bt *BackendT) UpmInstall() { + _, err := bt.Exec( + "upm", + "--lang", + bt.Backend.Name, + "install", + ) + + if err != nil { + bt.Fail("upm failed to install: %v", err) + } +} + +func (bt *BackendT) UpmListLockFile() []api.PkgInfo { + out, err := bt.Exec( + "upm", + "--lang", + bt.Backend.Name, + "list", + "--all", + "--format", + "json", + ) + + if err != nil { + bt.Fail("upm failed to list: %v", err) + } + + var results []api.PkgInfo + err = json.NewDecoder(strings.NewReader(out.Stdout)).Decode(&results) + if err != nil { + bt.Fail("failed to decode json: %v", err) + } + + return results +} + +func (bt *BackendT) UpmListSpecFile() []api.PkgInfo { + out, err := bt.Exec( + "upm", + "--lang", + bt.Backend.Name, + "list", + "--format", + "json", + ) + + if err != nil { + bt.Fail("upm failed to list: %v", err) + } + + var results []api.PkgInfo + err = json.NewDecoder(strings.NewReader(out.Stdout)).Decode(&results) + if err != nil { + bt.Fail("failed to decode json: %v", err) + } + + return results +} + +func (bt *BackendT) UpmLock() { + _, err := bt.Exec( + "upm", + "--lang", + bt.Backend.Name, + "lock", + ) + + if err != nil { + bt.Fail("upm failed to lock: %v", err) + } +} + +func (bt *BackendT) UpmPackageDir() string { + out, err := bt.Exec( + "upm", + "--lang", + bt.Backend.Name, + "show-package-dir", + ) + + if err != nil { + bt.Fail("upm failed to show package dir: %v", err) + } + + return strings.TrimSpace(out.Stdout) +} + +func (bt *BackendT) UpmRemove(pkgs ...string) { + beforeSpecDeps := bt.UpmListSpecFile() + + if len(beforeSpecDeps) < len(pkgs) { + bt.Fail("expected deps to be in spec file before remove %v", pkgs) + } + + specsExpectedToStay := []string{} + + for _, dep := range beforeSpecDeps { + removing := false + for _, pkg := range pkgs { + if dep.Name == pkg { + removing = true + break + } + } + + if !removing { + specsExpectedToStay = append(specsExpectedToStay, dep.Name) + } + } + + args := []string{ + "--lang", + bt.Backend.Name, + "remove", + } + _, err := bt.Exec( + "upm", + append(args, pkgs...)..., + ) + + if err != nil { + bt.Fail("upm failed to remove: %v", err) + } + + afterLockDeps := bt.UpmListLockFile() + afterSpecDeps := bt.UpmListSpecFile() + + if len(beforeSpecDeps) <= len(afterSpecDeps) { + bt.Fail("expected fewer deps in spec file after remove (before %d, after %d)", len(beforeSpecDeps), len(afterSpecDeps)) + } + + for _, pkg := range pkgs { + for _, dep := range afterLockDeps { + if dep.Name == pkg { + bt.Fail("expected %s not in lock file after remove", pkg) + } + } + + for _, dep := range afterSpecDeps { + if dep.Name == pkg { + bt.Fail("expected %s not in spec file after remove", pkg) + } + } + } + + for _, afterDep := range afterSpecDeps { + for ii, beforeDep := range specsExpectedToStay { + if afterDep.Name == beforeDep { + specsExpectedToStay = append(specsExpectedToStay[:ii], specsExpectedToStay[ii+1:]...) + break + } + } + } + + if len(specsExpectedToStay) != 0 { + bt.Fail("upm removed %v from spec file", specsExpectedToStay) + } +} + +func (bt *BackendT) UpmSearch(query, expectName string) { + out, err := bt.Exec( + "upm", + "--lang", + bt.Backend.Name, + "search", + "--format", + "json", + query, + ) + + if err != nil { + bt.t.Fatalf("upm failed to search: %v", err) + } + + var results []api.PkgInfo + err = json.NewDecoder(strings.NewReader(out.Stdout)).Decode(&results) + if err != nil { + bt.Fail("failed to decode json: %v", err) + } + + found := false + for _, result := range results { + if result.Name == expectName { + found = true + break + } + } + + if !found { + bt.Fail("expected %s in search results for query %s", expectName, query) + } +} + +func (bt *BackendT) UpmWhichLanguage() { + out, err := bt.Exec("upm", "which-language") + if err != nil { + bt.Fail("upm failed to detect language: %v", err) + } + + detected := strings.TrimSpace(out.Stdout) + if detected != bt.Backend.Name { + bt.Fail("expected %s, got %s", bt.Backend.Name, detected) + } +}