diff --git a/.prettierignore b/.prettierignore index fdbfccbf..d86785df 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,3 @@ # See: https://prettier.io/docs/en/ignore.html#ignoring-files-prettierignore -/test/testdata/test_all/golden/logs/ +/test/testdata/test_sync/golden/logs/ diff --git a/README.md b/README.md index 871aecf7..878e819f 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,11 @@ task go:test Create a `config.json` file (or edit example one). Same thing for `repos.txt` file. -Run with `go run sync_libraries.go` or `task go:build` and then `./libraries-repository-engine` +Run the following command to list the available command line interfaces: + +``` +./libraries-repository-engine help +``` ## Security diff --git a/internal/backup/backup.go b/internal/backup/backup.go new file mode 100644 index 00000000..fb759196 --- /dev/null +++ b/internal/backup/backup.go @@ -0,0 +1,107 @@ +// This file is part of libraries-repository-engine. +// +// Copyright 2021 ARDUINO SA (http://www.arduino.cc/) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// +// 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 backup does backup and restore of files. +package backup + +import ( + "github.com/arduino/go-paths-helper" +) + +type backup struct { + originalPath *paths.Path + backupPath *paths.Path +} + +var backupsFolder *paths.Path +var backups []backup + +// Backup saves a backup copy of the given path. +func Backup(originalPath *paths.Path) error { + if backupsFolder == nil { + // Create a parent folder to store all backups of this session. + var err error + if backupsFolder, err = paths.MkTempDir("", "libraries-repository-engine-backup"); err != nil { + return err + } + } + + // Create a folder for this individual backup item. + backupFolder, err := backupsFolder.MkTempDir("") + if err != nil { + return err + } + + backupPath := backupFolder.Join(originalPath.Base()) + + isDir, err := originalPath.IsDirCheck() + if err != nil { + return err + } + if isDir { + if err := originalPath.CopyDirTo(backupPath); err != nil { + return err + } + } else { + if err := originalPath.CopyTo(backupPath); err != nil { + return err + } + } + + backups = append(backups, backup{originalPath: originalPath, backupPath: backupPath}) + + return nil +} + +// Restore restores all backed up files. +func Restore() error { + for _, backup := range backups { + isDir, err := backup.backupPath.IsDirCheck() + if err != nil { + return err + } + if isDir { + if err := backup.originalPath.RemoveAll(); err != nil { + return err + } + if err := backup.backupPath.CopyDirTo(backup.originalPath); err != nil { + return err + } + } else { + if err := backup.backupPath.CopyTo(backup.originalPath); err != nil { + return err + } + } + } + + return nil +} + +// Clean deletes all the backup files. +func Clean() error { + if backupsFolder == nil { + return nil + } + + return backupsFolder.RemoveAll() +} diff --git a/internal/backup/backup_test.go b/internal/backup/backup_test.go new file mode 100644 index 00000000..af15b638 --- /dev/null +++ b/internal/backup/backup_test.go @@ -0,0 +1,94 @@ +// This file is part of libraries-repository-engine. +// +// Copyright 2021 ARDUINO SA (http://www.arduino.cc/) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// +// 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 backup + +import ( + "testing" + + "github.com/arduino/go-paths-helper" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var testDataPath string + +func TestAll(t *testing.T) { + var err error + originalsFolder, err := paths.MkTempDir("", "backup-test-testall") + require.NoError(t, err) + + // Generate test content. + originalContent := []byte("foo") + modifyFile, err := paths.WriteToTempFile(originalContent, originalsFolder, "") + require.NoError(t, err) + modifyFolder, err := originalsFolder.MkTempDir("") + require.NoError(t, err) + modifyFolderFile, err := paths.WriteToTempFile(originalContent, modifyFolder, "") + require.NoError(t, err) + deleteFile, err := paths.WriteToTempFile(originalContent, originalsFolder, "") + require.NoError(t, err) + deleteFolder, err := originalsFolder.MkTempDir("") + require.NoError(t, err) + deleteFolderFile, err := paths.WriteToTempFile(originalContent, deleteFolder, "") + require.NoError(t, err) + + // Backup test content. + err = Backup(modifyFile) + require.NoError(t, err) + err = Backup(modifyFolder) + require.NoError(t, err) + err = Backup(deleteFile) + require.NoError(t, err) + err = Backup(deleteFolder) + require.NoError(t, err) + + // Change the originals. + err = modifyFile.WriteFile([]byte("bar")) + require.NoError(t, err) + err = modifyFolderFile.WriteFile([]byte("bar")) + require.NoError(t, err) + err = deleteFile.Remove() + require.NoError(t, err) + err = deleteFolder.RemoveAll() + require.NoError(t, err) + + err = Restore() + require.NoError(t, err) + + // Verify changes to originals were reverted. + content, err := modifyFile.ReadFile() + require.NoError(t, err) + assert.Equal(t, originalContent, content) + + content, err = modifyFolderFile.ReadFile() + require.NoError(t, err) + assert.Equal(t, originalContent, content) + + assert.True(t, deleteFile.Exist()) + assert.True(t, deleteFolderFile.Exist()) + + // Clean the backups. + err = Clean() + require.NoError(t, err) +} diff --git a/internal/cli/remove.go b/internal/cli/remove.go new file mode 100644 index 00000000..2b1a9ebf --- /dev/null +++ b/internal/cli/remove.go @@ -0,0 +1,46 @@ +// This file is part of libraries-repository-engine. +// +// Copyright 2021 ARDUINO SA (http://www.arduino.cc/) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// +// 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 cli + +import ( + "github.com/arduino/libraries-repository-engine/internal/command/remove" + "github.com/spf13/cobra" +) + +// removeCmd defines the `remove` CLI subcommand. +var removeCmd = &cobra.Command{ + Short: "Remove libraries or releases", + Long: "Remove libraries or library releases from Library Manager", + DisableFlagsInUseLine: true, + Use: `remove [FLAG]... LIBRARY_NAME[@RELEASE]... + +Remove library name LIBRARY_NAME Library Manager content entirely. +-or- +Remove release RELEASE of library name LIBRARY_NAME from the Library Manager content.`, + Run: remove.Run, +} + +func init() { + rootCmd.AddCommand(removeCmd) +} diff --git a/internal/command/remove/remove.go b/internal/command/remove/remove.go new file mode 100644 index 00000000..7870e06e --- /dev/null +++ b/internal/command/remove/remove.go @@ -0,0 +1,219 @@ +// This file is part of libraries-repository-engine. +// +// Copyright 2021 ARDUINO SA (http://www.arduino.cc/) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// +// 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 remove implements the `remove` CLI subcommand used by the maintainer for removals of libraries or releases. +package remove + +import ( + "fmt" + "os" + "strings" + + "github.com/arduino/go-paths-helper" + "github.com/arduino/libraries-repository-engine/internal/backup" + "github.com/arduino/libraries-repository-engine/internal/configuration" + "github.com/arduino/libraries-repository-engine/internal/feedback" + "github.com/arduino/libraries-repository-engine/internal/libraries" + "github.com/arduino/libraries-repository-engine/internal/libraries/archive" + "github.com/arduino/libraries-repository-engine/internal/libraries/db" + "github.com/arduino/libraries-repository-engine/internal/libraries/metadata" + + "github.com/spf13/cobra" +) + +var config *configuration.Config +var librariesDb *db.DB +var libraryData *db.Library + +// Run executes the command. +func Run(command *cobra.Command, cliArguments []string) { + config = configuration.ReadConf(command.Flags()) + + if len(cliArguments) == 0 { + feedback.Error("LIBRARY_NAME argument is required") + os.Exit(1) + } + + librariesDBPath := paths.New(config.LibrariesDB) + exist, err := librariesDBPath.ExistCheck() + if err != nil { + feedback.Errorf("While checking existence of database file: %s", err) + os.Exit(1) + } + if !exist { + feedback.Errorf("Database file not found at %s. Check the LibrariesDB configuration value.", librariesDBPath) + os.Exit(1) + } + + if err := backup.Backup(librariesDBPath); err != nil { + feedback.Errorf("While backing up database: %s", err) + os.Exit(1) + } + + librariesDb = db.Init(config.LibrariesDB) + + restore, err := removals(cliArguments) + if err != nil { + feedback.Error(err) + if restore { + if err := backup.Restore(); err != nil { + feedback.Errorf("While restoring the content from backup: %s", err) + } + fmt.Println("Original files were restored.") + } else { + if err := backup.Clean(); err != nil { + feedback.Errorf("While cleaning up the backup content: %s", err) + } + } + os.Exit(1) + } + + if err := librariesDb.Commit(); err != nil { + feedback.Errorf("While saving changes to database: %s", err) + if err := backup.Restore(); err != nil { + feedback.Errorf("While restoring the content from backup: %s", err) + } + fmt.Println("Original files were restored.") + os.Exit(1) + } + + if err := backup.Clean(); err != nil { + feedback.Errorf("While cleaning up the backup files: %s", err) + os.Exit(1) + } + + fmt.Println("Success!") +} + +func removals(libraryReferences []string) (bool, error) { + for _, libraryReference := range libraryReferences { + referenceComponents := strings.SplitN(libraryReference, "@", 2) + libraryName := referenceComponents[0] + var libraryVersion string + if len(referenceComponents) > 1 { + if referenceComponents[1] == "" { + return false, fmt.Errorf("Missing version for library name %s. For full removal, omit the '@'", libraryName) + } + libraryVersion = referenceComponents[1] + } + + if !librariesDb.HasLibrary(libraryName) { + return false, fmt.Errorf("Library name %s not found", libraryName) + } + + var err error + libraryData, err = librariesDb.FindLibrary(libraryName) + if err != nil { + return true, err + } + + if libraryVersion == "" { + // Remove the library entirely. + if err := removeLibrary(libraryName); err != nil { + return true, err + } + } else { + // Remove only a specific release of the library. + if err := removeRelease(libraryName, libraryVersion); err != nil { + return true, err + } + } + } + + return false, nil +} + +func removeLibrary(libraryName string) error { + fmt.Printf("Removing %s\n", libraryName) + + // Remove the library's release archive files. + releasesData := librariesDb.FindReleasesOfLibrary(libraryData) + for _, releaseData := range releasesData { + if err := removeReleaseArchive(releaseData.Version.String()); err != nil { + return err + } + } + + // Remove the library and its releases from database. + if err := librariesDb.RemoveLibrary(libraryName); err != nil { + return err + } + + // Remove the library Git clone folder. + libraryRegistration := libraries.Repo{URL: libraryData.Repository} + gitCloneSubfolder, err := libraryRegistration.AsFolder() + if err != nil { + return err + } + gitClonePath := paths.New(config.GitClonesFolder, gitCloneSubfolder) + if err := backup.Backup(gitClonePath); err != nil { + return fmt.Errorf("While backing up library's Git clone: %w", err) + } + if err := gitClonePath.RemoveAll(); err != nil { + return fmt.Errorf("While removing library Git clone: %s", err) + } + + return nil +} + +func removeRelease(libraryName string, version string) error { + fmt.Printf("Removing %s@%s\n", libraryName, version) + + if !librariesDb.HasReleaseByNameVersion(libraryName, version) { + return fmt.Errorf("Library release %s@%s not found", libraryName, version) + } + + // Remove the release archive file. + if err := removeReleaseArchive(version); err != nil { + return err + } + + // Remove the release from the database. + if err := librariesDb.RemoveReleaseByNameVersion(libraryName, version); err != nil { + return err + } + + return nil +} + +func removeReleaseArchive(version string) error { + repositoryObject := libraries.Repository{URL: libraryData.Repository} + libraryMetadata := metadata.LibraryMetadata{ + Name: libraryData.Name, + Version: version, + } + archiveObject, err := archive.New(&repositoryObject, &libraryMetadata, config) + if err != nil { + panic(err) + } + + archivePath := paths.New(archiveObject.Path) + if err := backup.Backup(archivePath); err != nil { + return fmt.Errorf("While backing up library release archive: %w", err) + } + if err := archivePath.RemoveAll(); err != nil { + return fmt.Errorf("While removing library release archive: %s", err) + } + + return nil +} diff --git a/internal/libraries/db/db.go b/internal/libraries/db/db.go index 28e63368..a4f8dc8f 100644 --- a/internal/libraries/db/db.go +++ b/internal/libraries/db/db.go @@ -96,6 +96,30 @@ func (db *DB) AddLibrary(library *Library) error { return nil } +// RemoveLibrary removes a library and all its releases from the database. +func (db *DB) RemoveLibrary(libraryName string) error { + db.mutex.Lock() + defer db.mutex.Unlock() + return db.removeLibrary(libraryName) +} + +func (db *DB) removeLibrary(libraryName string) error { + found := false + for i := range db.Libraries { + for i < len(db.Libraries) && db.Libraries[i].Name == libraryName { + found = true + db.Libraries = append(db.Libraries[:i], db.Libraries[i+1:]...) + } + } + if !found { + return errors.New("library not found") + } + + db.removeReleases(libraryName) // It's OK if no releases were found. + + return nil +} + // HasLibrary returns whether the database already contains the given library. func (db *DB) HasLibrary(libraryName string) bool { db.mutex.Lock() @@ -149,6 +173,28 @@ func (db *DB) AddRelease(release *Release, repoURL string) error { return nil } +// RemoveReleaseByNameVersion removes the given library release from the database. +func (db *DB) RemoveReleaseByNameVersion(libraryName string, libraryVersion string) error { + db.mutex.Lock() + defer db.mutex.Unlock() + return db.removeReleaseByNameVersion(libraryName, libraryVersion) +} + +func (db *DB) removeReleaseByNameVersion(libraryName string, libraryVersion string) error { + found := false + for i, release := range db.Releases { + if release.LibraryName == libraryName && release.Version.String() == libraryVersion { + found = true + db.Releases = append(db.Releases[:i], db.Releases[i+1:]...) + } + } + if !found { + return errors.New("release not found") + } + + return nil +} + // HasReleaseByNameVersion returns whether the database contains a release for the given library and version number. func (db *DB) HasReleaseByNameVersion(libraryName string, libraryVersion string) bool { db.mutex.Lock() @@ -276,6 +322,28 @@ func (db *DB) findReleasesOfLibrary(lib *Library) []*Release { return releases } +// RemoveReleases removes all releases of a library from the database. +func (db *DB) RemoveReleases(libraryName string) error { + db.mutex.Lock() + defer db.mutex.Unlock() + return db.removeReleases(libraryName) +} + +func (db *DB) removeReleases(libraryName string) error { + found := false + for i := range db.Releases { + for i < len(db.Releases) && db.Releases[i].LibraryName == libraryName { + found = true + db.Releases = append(db.Releases[:i], db.Releases[i+1:]...) + } + } + if !found { + return errors.New("releases not found") + } + + return nil +} + // Commit saves the database to disk. func (db *DB) Commit() error { return db.SaveToFile() diff --git a/internal/libraries/db/db_test.go b/internal/libraries/db/db_test.go new file mode 100644 index 00000000..dd8e57c5 --- /dev/null +++ b/internal/libraries/db/db_test.go @@ -0,0 +1,194 @@ +// This file is part of libraries-repository-engine. +// +// Copyright 2021 ARDUINO SA (http://www.arduino.cc/) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// +// 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 db + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func testerDB() *DB { + tDB := DB{ + Libraries: []*Library{ + { + Name: "FooLib", + Repository: "https://github.com/Bar/FooLib.git", + SupportLevel: "", + }, + { + Name: "BazLib", + Repository: "https://github.com/Bar/BazLib.git", + SupportLevel: "", + }, + { + Name: "QuxLib", + Repository: "https://github.com/Zeb/QuxLib.git", + SupportLevel: "", + }, + }, + Releases: []*Release{ + { + LibraryName: "FooLib", + Version: Version{"1.0.0"}, + Author: "Barthor", + Maintainer: "Bartainer", + License: "MIT", + Sentence: "asdf", + Paragraph: "zxcv", + Website: "https://example.com", + Category: "Other", + Architectures: []string{"avr"}, + Types: []string{"Contributed"}, + URL: "http://www.example.com/libraries/github.com/Bar/FooLib-1.0.0.zip", + ArchiveFileName: "FooLib-1.0.0.zip", + Size: 123, + Checksum: "SHA-256:887f897cfb1818a53652aef39c2a4b8de3c69c805520b2953a562a787b422420", + Includes: []string{"FooLib.h"}, + Dependencies: []*Dependency{ + { + Name: "BazLib", + Version: "2.0.0", + }, + }, + Log: "Some log messages", + }, + { + LibraryName: "BazLib", + Version: Version{"2.0.0"}, + Author: "Barthor", + Maintainer: "Bartainer", + License: "MIT", + Sentence: "asdf", + Paragraph: "zxcv", + Website: "https://example.com", + Category: "Other", + Architectures: []string{"avr"}, + Types: []string{"Contributed"}, + URL: "http://www.example.com/libraries/github.com/Bar/BazLib-2.0.0.zip", + ArchiveFileName: "BazLib-2.0.0.zip", + Size: 123, + Checksum: "SHA-256:887f897cfb1818a53652aef39c2a4b8de3c69c805520b2953a562a787b422420", + Includes: []string{"BazLib.h"}, + Dependencies: []*Dependency{}, + Log: "Some log messages", + }, + { + LibraryName: "BazLib", + Version: Version{"2.1.0"}, + Author: "Barthor", + Maintainer: "Bartainer", + License: "MIT", + Sentence: "asdf", + Paragraph: "zxcv", + Website: "https://example.com", + Category: "Other", + Architectures: []string{"avr"}, + Types: []string{"Contributed"}, + URL: "http://www.example.com/libraries/github.com/Bar/BazLib-2.1.0.zip", + ArchiveFileName: "BazLib-2.1.0.zip", + Size: 123, + Checksum: "SHA-256:887f897cfb1818a53652aef39c2a4b8de3c69c805520b2953a562a787b422420", + Includes: []string{"BazLib.h"}, + Dependencies: []*Dependency{}, + Log: "Some log messages", + }, + { + LibraryName: "FooLib", + Version: Version{"1.1.0"}, + Author: "Barthor", + Maintainer: "Bartainer", + License: "MIT", + Sentence: "asdf", + Paragraph: "zxcv", + Website: "https://example.com", + Category: "Other", + Architectures: []string{"avr"}, + Types: []string{"Contributed"}, + URL: "http://www.example.com/libraries/github.com/Bar/FooLib-1.1.0.zip", + ArchiveFileName: "FooLib-1.1.0.zip", + Size: 123, + Checksum: "SHA-256:887f897cfb1818a53652aef39c2a4b8de3c69c805520b2953a562a787b422420", + Includes: []string{"FooLib.h"}, + Dependencies: []*Dependency{ + { + Name: "BazLib", + Version: "", + }, + }, + Log: "Some log messages", + }, + }, + libraryFile: "some-file.json", + } + + return &tDB +} + +func TestRemoveLibrary(t *testing.T) { + testDB := testerDB() + assert.True(t, testDB.HasLibrary("FooLib")) + assert.True(t, testDB.HasReleaseByNameVersion("FooLib", "1.0.0")) + err := testDB.RemoveLibrary("FooLib") + require.NoError(t, err) + assert.False(t, testDB.HasLibrary("FooLib")) + assert.False(t, testDB.HasReleaseByNameVersion("FooLib", "1.0.0")) + assert.False(t, testDB.HasReleaseByNameVersion("FooLib", "1.1.0")) + + assert.True(t, testDB.HasLibrary("QuxLib")) + err = testDB.RemoveLibrary("QuxLib") + require.NoError(t, err) + assert.False(t, testDB.HasLibrary("QuxLib")) + + err = testDB.RemoveLibrary("nonexistent") + assert.Error(t, err) +} + +func TestRemoveReleaseByNameVersion(t *testing.T) { + testDB := testerDB() + assert.True(t, testDB.HasReleaseByNameVersion("FooLib", "1.0.0")) + assert.True(t, testDB.HasReleaseByNameVersion("FooLib", "1.1.0")) + err := testDB.RemoveReleaseByNameVersion("FooLib", "1.0.0") + require.NoError(t, err) + assert.False(t, testDB.HasReleaseByNameVersion("FooLib", "1.0.0")) + assert.True(t, testDB.HasReleaseByNameVersion("FooLib", "1.1.0")) + + err = testDB.RemoveReleaseByNameVersion("nonexistent", "1.0.0") + assert.Error(t, err) + err = testDB.RemoveReleaseByNameVersion("FooLib", "99.99.99") + assert.Error(t, err) +} + +func TestRemoveReleases(t *testing.T) { + testDB := testerDB() + assert.True(t, testDB.HasReleaseByNameVersion("FooLib", "1.0.0")) + err := testDB.RemoveReleases("FooLib") + require.NoError(t, err) + assert.False(t, testDB.HasReleaseByNameVersion("FooLib", "1.0.0")) + assert.False(t, testDB.HasReleaseByNameVersion("FooLib", "1.1.0")) + + err = testDB.RemoveReleases("nonexistent") + assert.Error(t, err) +} diff --git a/test/conftest.py b/test/conftest.py new file mode 100644 index 00000000..c4519938 --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,123 @@ +# Source: +# https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/test-integration/test_all.py +# Copyright 2021 ARDUINO SA (http://www.arduino.cc/) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# 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. +# +import pytest +import pathlib +import platform +import typing +import invoke.context +import json + + +@pytest.fixture +def configuration(working_dir): + """Create a libraries-repository-engine configuration file and return an object containing its data and path.""" + working_dir_path = pathlib.Path(working_dir) + + # This is based on the `Librariesv2` production job's config. + data = { + "BaseDownloadUrl": "https://downloads.arduino.cc/libraries/", + "LibrariesFolder": working_dir_path.joinpath("libraries").as_posix(), + "LogsFolder": working_dir_path.joinpath("ci-logs", "libraries", "logs").as_posix(), + "LibrariesDB": working_dir_path.joinpath("db.json").as_posix(), + "LibrariesIndex": working_dir_path.joinpath("libraries", "library_index.json").as_posix(), + "GitClonesFolder": working_dir_path.joinpath("gitclones").as_posix(), + # I was unable to get clamdscan working in the GitHub Actions runner, but the tests should pass with this set to + # False when run on a machine with ClamAV installed. + "DoNotRunClamav": True, + # Arduino Lint should be installed under PATH + "ArduinoLintPath": "", + } + + # Generate configuration file + path = working_dir_path.joinpath("config.json") + with path.open("w", encoding="utf-8") as configuration_file: + json.dump(obj=data, fp=configuration_file, indent=2) + + class Object: + """Container for libraries-repository-engine configuration data. + + Keyword arguments: + data -- dictionary of configuration data + path -- path of the configuration file + """ + + def __init__(self, data, path): + self.data = data + self.path = path + + return Object(data=data, path=path) + + +@pytest.fixture(scope="function") +def run_command(pytestconfig, working_dir) -> typing.Callable[..., invoke.runners.Result]: + """Provide a wrapper around invoke's `run` API so that every test will work in the same temporary folder. + + Useful reference: + http://docs.pyinvoke.org/en/1.4/api/runners.html#invoke.runners.Result + """ + + executable_path = pathlib.Path(pytestconfig.rootdir).parent / "libraries-repository-engine" + + def _run( + cmd: list, + custom_working_dir: typing.Optional[str] = None, + custom_env: typing.Optional[dict] = None, + ) -> invoke.runners.Result: + if cmd is None: + cmd = [] + if not custom_working_dir: + custom_working_dir = working_dir + quoted_cmd = [] + for token in cmd: + quoted_cmd.append(f'"{token}"') + cli_full_line = '"{}" {}'.format(executable_path, " ".join(quoted_cmd)) + run_context = invoke.context.Context() + # It might happen that we need to change directories between drives on Windows, + # in that case the "/d" flag must be used otherwise directory wouldn't change + cd_command = "cd" + if platform.system() == "Windows": + cd_command += " /d" + # Context.cd() is not used since it doesn't work correctly on Windows. + # It escapes spaces in the path using "\ " but it doesn't always work, + # wrapping the path in quotation marks is the safest approach + with run_context.prefix(f'{cd_command} "{custom_working_dir}"'): + return run_context.run( + command=cli_full_line, + echo=False, + hide=True, + warn=True, + env=custom_env, + encoding="utf-8", + ) + + return _run + + +@pytest.fixture(scope="function") +def working_dir(tmpdir_factory) -> str: + """Create a temporary folder for the test to run in. It will be created before running each test and deleted at the + end. This way all the tests work in isolation. + """ + work_dir = tmpdir_factory.mktemp(basename="TestWorkingDir") + yield str(work_dir) diff --git a/test/test_remove.py b/test/test_remove.py new file mode 100644 index 00000000..6e5aaecd --- /dev/null +++ b/test/test_remove.py @@ -0,0 +1,284 @@ +# Copyright 2021 ARDUINO SA (http://www.arduino.cc/) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# 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. +# + +import json +import pathlib + +test_data_path = pathlib.Path(__file__).resolve().parent.joinpath("testdata") + + +def test_help(run_command): + """Test the command line help.""" + # Run the `help modify` command + engine_command = [ + "help", + "remove", + ] + result = run_command(cmd=engine_command) + assert result.ok + assert "help for remove" in result.stdout + + # --help flag + engine_command = [ + "remove", + "--help", + ] + result = run_command(cmd=engine_command) + assert result.ok + assert "help for remove" in result.stdout + + +def test_invalid_flag(configuration, run_command): + """Test the command's handling of invalid flags.""" + invalid_flag = "--some-bad-flag" + engine_command = [ + "remove", + invalid_flag, + "--config-file", + configuration.path, + "SpacebrewYun", + ] + result = run_command(cmd=engine_command) + assert not result.ok + assert f"unknown flag: {invalid_flag}" in result.stderr + + +def test_missing_library_name_arg(configuration, run_command): + """Test the command's handling of missing LIBRARY_NAME argument.""" + engine_command = [ + "remove", + "--config-file", + configuration.path, + ] + result = run_command(cmd=engine_command) + assert not result.ok + assert "LIBRARY_NAME argument is required" in result.stderr + + +def test_database_file_not_found(configuration, run_command): + """Test the command's handling of incorrect LibrariesDB configuration.""" + engine_command = [ + "remove", + "--config-file", + configuration.path, + "SpacebrewYun", + ] + result = run_command(cmd=engine_command) + assert not result.ok + assert "Database file not found at {db_path}".format(db_path=configuration.data["LibrariesDB"]) in result.stderr + + +def test_remove_basic(configuration, run_command): + """Test the basic functionality of the `remove` command.""" + # Run the sync command to generate test data + engine_command = [ + "sync", + "--config-file", + configuration.path, + test_data_path.joinpath("test_remove", "test_remove_basic", "repos.txt"), + ] + result = run_command(cmd=engine_command) + assert result.ok + assert pathlib.Path(configuration.data["LibrariesDB"]).exists() + + # Release reference syntax with missing version + library_name = "SpacebrewYun" + engine_command = [ + "remove", + "--config-file", + configuration.path, + f"{library_name}@", + ] + result = run_command(cmd=engine_command) + assert not result.ok + assert f"Missing version for library name {library_name}" in result.stderr + + # LIBRARY_NAME argument not in DB + nonexistent_library_name = "nonexistent" + engine_command = [ + "remove", + "--config-file", + configuration.path, + nonexistent_library_name, + ] + result = run_command(cmd=engine_command) + assert not result.ok + assert f"{nonexistent_library_name} not found" in result.stderr + + # LIBRARY_NAME@VERSION argument not in DB + library_name = "SpacebrewYun" + version = "99.99.99" + engine_command = [ + "remove", + "--config-file", + configuration.path, + f"{library_name}@{version}", + ] + result = run_command(cmd=engine_command) + assert not result.ok + assert f"Library release {library_name}@{version} not found" in result.stderr + + +def test_remove(configuration, run_command, working_dir): + """Test the the removal of an entire library.""" + # Run the sync command to generate test data + engine_command = [ + "sync", + "--config-file", + configuration.path, + test_data_path.joinpath("test_remove", "repos.txt"), + ] + result = run_command(cmd=engine_command) + assert result.ok + assert pathlib.Path(configuration.data["LibrariesDB"]).exists() + + def git_clone_path_exists(host, owner, repo_name): + return pathlib.Path(configuration.data["GitClonesFolder"]).joinpath(host, owner, repo_name).exists() + + def release_archive_path_exists(host, owner, library_name, version): + sanitized_library_name = library_name.replace(" ", "_") + return ( + pathlib.Path(configuration.data["LibrariesFolder"]) + .joinpath(host, owner, f"{sanitized_library_name}-{version}.zip") + .exists() + ) + + def db_has_library(library_name): + with pathlib.Path(configuration.data["LibrariesDB"]).open(mode="r", encoding="utf-8") as library_db_file: + library_db = json.load(fp=library_db_file) + + for library in library_db["Libraries"]: + if library["Name"] == library_name: + return True + + return False + + def db_has_release(library_name, version): + with pathlib.Path(configuration.data["LibrariesDB"]).open(mode="r", encoding="utf-8") as library_db_file: + library_db = json.load(fp=library_db_file) + + for release in library_db["Releases"]: + if release["LibraryName"] == library_name and release["Version"] == version: + return True + + return False + + # Verify the pre-command environment is as expected + assert git_clone_path_exists(host="github.com", owner="arduino-libraries", repo_name="ArduinoCloudThing") + assert git_clone_path_exists(host="github.com", owner="arduino-libraries", repo_name="ArduinoIoTCloudBearSSL") + assert git_clone_path_exists( + host="github.com", owner="arduino-libraries", repo_name="UnoWiFi-Developer-Edition-Lib" + ) + assert git_clone_path_exists(host="github.com", owner="arduino-libraries", repo_name="SpacebrewYun") + + # Note: The "ArduinoCloudThing" library is used as a "canary" to make sure that other libraries are not affected by + # the removal process, so I don't bother to check all of its many releases + # (there is lack of selection of appropriate libraries for test data) + assert release_archive_path_exists( + host="github.com", owner="arduino-libraries", library_name="ArduinoCloudThing", version="1.3.1" + ) + assert release_archive_path_exists( + host="github.com", owner="arduino-libraries", library_name="ArduinoIoTCloudBearSSL", version="1.1.1" + ) + assert release_archive_path_exists( + host="github.com", owner="arduino-libraries", library_name="ArduinoIoTCloudBearSSL", version="1.1.2" + ) + assert release_archive_path_exists( + host="github.com", owner="arduino-libraries", library_name="Arduino Uno WiFi Dev Ed Library", version="0.0.3" + ) + assert release_archive_path_exists( + host="github.com", owner="arduino-libraries", library_name="SpacebrewYun", version="1.0.0" + ) + assert release_archive_path_exists( + host="github.com", owner="arduino-libraries", library_name="SpacebrewYun", version="1.0.1" + ) + assert release_archive_path_exists( + host="github.com", owner="arduino-libraries", library_name="SpacebrewYun", version="1.0.2" + ) + + assert db_has_library(library_name="ArduinoCloudThing") + assert db_has_library(library_name="ArduinoIoTCloudBearSSL") + assert db_has_library(library_name="Arduino Uno WiFi Dev Ed Library") + assert db_has_library(library_name="SpacebrewYun") + + assert db_has_release(library_name="ArduinoCloudThing", version="1.3.1") + assert db_has_release(library_name="ArduinoIoTCloudBearSSL", version="1.1.1") + assert db_has_release(library_name="ArduinoIoTCloudBearSSL", version="1.1.2") + assert db_has_release(library_name="Arduino Uno WiFi Dev Ed Library", version="0.0.3") + assert db_has_release(library_name="SpacebrewYun", version="1.0.0") + assert db_has_release(library_name="SpacebrewYun", version="1.0.1") + assert db_has_release(library_name="SpacebrewYun", version="1.0.2") + + # Run a remove command + engine_command = [ + "remove", + "--config-file", + configuration.path, + "ArduinoIoTCloudBearSSL", + "Arduino Uno WiFi Dev Ed Library@0.0.3", + "SpacebrewYun@1.0.1", + ] + result = run_command(cmd=engine_command) + assert result.ok + + # Verify the post-command environment is as expected + assert git_clone_path_exists(host="github.com", owner="arduino-libraries", repo_name="ArduinoCloudThing") + assert not git_clone_path_exists(host="github.com", owner="arduino-libraries", repo_name="ArduinoIoTCloudBearSSL") + assert git_clone_path_exists( + host="github.com", owner="arduino-libraries", repo_name="UnoWiFi-Developer-Edition-Lib" + ) + assert git_clone_path_exists(host="github.com", owner="arduino-libraries", repo_name="SpacebrewYun") + + assert release_archive_path_exists( + host="github.com", owner="arduino-libraries", library_name="ArduinoCloudThing", version="1.3.1" + ) + assert not release_archive_path_exists( + host="github.com", owner="arduino-libraries", library_name="ArduinoIoTCloudBearSSL", version="1.1.1" + ) + assert not release_archive_path_exists( + host="github.com", owner="arduino-libraries", library_name="ArduinoIoTCloudBearSSL", version="1.1.2" + ) + assert not release_archive_path_exists( + host="github.com", owner="arduino-libraries", library_name="Arduino Uno WiFi Dev Ed Library", version="0.0.3" + ) + assert release_archive_path_exists( + host="github.com", owner="arduino-libraries", library_name="SpacebrewYun", version="1.0.0" + ) + assert not release_archive_path_exists( + host="github.com", owner="arduino-libraries", library_name="SpacebrewYun", version="1.0.1" + ) + assert release_archive_path_exists( + host="github.com", owner="arduino-libraries", library_name="SpacebrewYun", version="1.0.2" + ) + + assert db_has_library(library_name="ArduinoCloudThing") + assert not db_has_library(library_name="ArduinoIoTCloudBearSSL") + assert db_has_library(library_name="Arduino Uno WiFi Dev Ed Library") + assert db_has_library(library_name="SpacebrewYun") + + assert db_has_release(library_name="ArduinoCloudThing", version="1.3.1") + assert not db_has_release(library_name="ArduinoIoTCloudBearSSL", version="1.1.1") + assert not db_has_release(library_name="ArduinoIoTCloudBearSSL", version="1.1.2") + assert not db_has_release(library_name="Arduino Uno WiFi Dev Ed Library", version="0.0.3") + assert db_has_release(library_name="SpacebrewYun", version="1.0.0") + assert not db_has_release(library_name="SpacebrewYun", version="1.0.1") + assert db_has_release(library_name="SpacebrewYun", version="1.0.2") diff --git a/test/test_all.py b/test/test_sync.py similarity index 66% rename from test/test_all.py rename to test/test_sync.py index ecd038e3..bae8b562 100644 --- a/test/test_all.py +++ b/test/test_sync.py @@ -1,5 +1,3 @@ -# Source: -# https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/test-integration/test_all.py # Copyright 2021 ARDUINO SA (http://www.arduino.cc/) # # This program is free software: you can redistribute it and/or modify @@ -27,42 +25,18 @@ import hashlib import json import pathlib -import platform -import typing import math -import invoke.context -import pytest - test_data_path = pathlib.Path(__file__).resolve().parent.joinpath("testdata") size_comparison_tolerance = 0.03 # Maximum allowed archive size difference ratio -def test_all(run_command, working_dir): - working_dir_path = pathlib.Path(working_dir) - configuration = { - "BaseDownloadUrl": "http://www.example.com/libraries/", - "LibrariesFolder": working_dir_path.joinpath("libraries").as_posix(), - "LogsFolder": working_dir_path.joinpath("logs").as_posix(), - "LibrariesDB": working_dir_path.joinpath("libraries_db.json").as_posix(), - "LibrariesIndex": working_dir_path.joinpath("libraries", "library_index.json").as_posix(), - "GitClonesFolder": working_dir_path.joinpath("gitclones").as_posix(), - # I was unable to get clamdscan working in the GitHub Actions runner, but the tests should pass with this set to - # False when run on a machine with ClamAV installed. - "DoNotRunClamav": True, - # Arduino Lint should be installed under PATH - "ArduinoLintPath": "", - } - - # Generate configuration file - with working_dir_path.joinpath("config.json").open("w", encoding="utf-8") as configuration_file: - json.dump(obj=configuration, fp=configuration_file, indent=2) - +def test_sync(configuration, run_command): libraries_repository_engine_command = [ "sync", "--config-file", - working_dir_path.joinpath("config.json"), - test_data_path.joinpath("test_all", "repos.txt"), + configuration.path, + test_data_path.joinpath("test_sync", "repos.txt"), ] # Run the engine @@ -70,38 +44,38 @@ def test_all(run_command, working_dir): assert result.ok # Test fresh output - check_libraries(configuration=configuration) + check_libraries(configuration=configuration.data) check_logs( - configuration=configuration, - golden_logs_parent_path=test_data_path.joinpath("test_all", "golden", "logs", "generate"), + configuration=configuration.data, + golden_logs_parent_path=test_data_path.joinpath("test_sync", "golden", "logs", "generate"), logs_subpath=pathlib.Path("github.com", "arduino-libraries", "ArduinoCloudThing", "index.html"), ) check_logs( - configuration=configuration, - golden_logs_parent_path=test_data_path.joinpath("test_all", "golden", "logs", "generate"), + configuration=configuration.data, + golden_logs_parent_path=test_data_path.joinpath("test_sync", "golden", "logs", "generate"), logs_subpath=pathlib.Path("github.com", "arduino-libraries", "SpacebrewYun", "index.html"), ) - check_db(configuration=configuration) - check_index(configuration=configuration) + check_db(configuration=configuration.data) + check_index(configuration=configuration.data) # Run the engine again result = run_command(cmd=libraries_repository_engine_command) assert result.ok # Test the updated output - check_libraries(configuration=configuration) + check_libraries(configuration=configuration.data) check_logs( - configuration=configuration, - golden_logs_parent_path=test_data_path.joinpath("test_all", "golden", "logs", "update"), + configuration=configuration.data, + golden_logs_parent_path=test_data_path.joinpath("test_sync", "golden", "logs", "update"), logs_subpath=pathlib.Path("github.com", "arduino-libraries", "ArduinoCloudThing", "index.html"), ) check_logs( - configuration=configuration, - golden_logs_parent_path=test_data_path.joinpath("test_all", "golden", "logs", "update"), + configuration=configuration.data, + golden_logs_parent_path=test_data_path.joinpath("test_sync", "golden", "logs", "update"), logs_subpath=pathlib.Path("github.com", "arduino-libraries", "SpacebrewYun", "index.html"), ) - check_db(configuration=configuration) - check_index(configuration=configuration) + check_db(configuration=configuration.data) + check_index(configuration=configuration.data) def check_libraries(configuration): @@ -190,7 +164,7 @@ def check_db(configuration): release["Log"] = "\n".join([line.rstrip() for line in release["Log"].splitlines()]) # Load golden db - golden_db_template = test_data_path.joinpath("test_all", "golden", "db.json").read_text(encoding="utf-8") + golden_db_template = test_data_path.joinpath("test_sync", "golden", "db.json").read_text(encoding="utf-8") # Fill in mutable content golden_db_string = string.Template(template=golden_db_template).substitute( base_download_url=configuration["BaseDownloadUrl"], @@ -248,7 +222,7 @@ def check_index(configuration): release["checksum"] = checksum_placeholder # Load golden index - golden_library_index_template = test_data_path.joinpath("test_all", "golden", "library_index.json").read_text( + golden_library_index_template = test_data_path.joinpath("test_sync", "golden", "library_index.json").read_text( encoding="utf-8" ) # Fill in mutable content @@ -283,28 +257,11 @@ def check_index(configuration): # The engine's Git code struggles to get a clean checkout of releases under some circumstances. -def test_clean_checkout(run_command, working_dir): - working_dir_path = pathlib.Path(working_dir) - configuration = { - "BaseDownloadUrl": "http://www.example.com/libraries/", - "LibrariesFolder": working_dir_path.joinpath("libraries").as_posix(), - "LogsFolder": working_dir_path.joinpath("logs").as_posix(), - "LibrariesDB": working_dir_path.joinpath("libraries_db.json").as_posix(), - "LibrariesIndex": working_dir_path.joinpath("libraries", "library_index.json").as_posix(), - "GitClonesFolder": working_dir_path.joinpath("gitclones").as_posix(), - "DoNotRunClamav": True, - # Arduino Lint should be installed under PATH - "ArduinoLintPath": "", - } - - # Generate configuration file - with working_dir_path.joinpath("config.json").open("w", encoding="utf-8") as configuration_file: - json.dump(obj=configuration, fp=configuration_file, indent=2) - +def test_clean_checkout(configuration, run_command): libraries_repository_engine_command = [ "sync", "--config-file", - working_dir_path.joinpath("config.json"), + configuration.path, test_data_path.joinpath("test_clean_checkout", "repos.txt"), ] @@ -313,63 +270,9 @@ def test_clean_checkout(run_command, working_dir): assert result.ok # Load generated index - with pathlib.Path(configuration["LibrariesIndex"]).open(mode="r", encoding="utf-8") as library_index_file: + with pathlib.Path(configuration.data["LibrariesIndex"]).open(mode="r", encoding="utf-8") as library_index_file: library_index = json.load(fp=library_index_file) for release in library_index["libraries"]: # ssd1306@1.0.0 contains a .exe and so should fail validation. assert not (release["name"] == "ssd1306" and release["version"] == "1.0.0") - - -@pytest.fixture(scope="function") -def run_command(pytestconfig, working_dir) -> typing.Callable[..., invoke.runners.Result]: - """Provide a wrapper around invoke's `run` API so that every test will work in the same temporary folder. - - Useful reference: - http://docs.pyinvoke.org/en/1.4/api/runners.html#invoke.runners.Result - """ - - executable_path = pathlib.Path(pytestconfig.rootdir).parent / "libraries-repository-engine" - - def _run( - cmd: list, - custom_working_dir: typing.Optional[str] = None, - custom_env: typing.Optional[dict] = None, - ) -> invoke.runners.Result: - if cmd is None: - cmd = [] - if not custom_working_dir: - custom_working_dir = working_dir - quoted_cmd = [] - for token in cmd: - quoted_cmd.append(f'"{token}"') - cli_full_line = '"{}" {}'.format(executable_path, " ".join(quoted_cmd)) - run_context = invoke.context.Context() - # It might happen that we need to change directories between drives on Windows, - # in that case the "/d" flag must be used otherwise directory wouldn't change - cd_command = "cd" - if platform.system() == "Windows": - cd_command += " /d" - # Context.cd() is not used since it doesn't work correctly on Windows. - # It escapes spaces in the path using "\ " but it doesn't always work, - # wrapping the path in quotation marks is the safest approach - with run_context.prefix(f'{cd_command} "{custom_working_dir}"'): - return run_context.run( - command=cli_full_line, - echo=False, - hide=True, - warn=True, - env=custom_env, - encoding="utf-8", - ) - - return _run - - -@pytest.fixture(scope="function") -def working_dir(tmpdir_factory) -> str: - """Create a temporary folder for the test to run in. It will be created before running each test and deleted at the - end. This way all the tests work in isolation. - """ - work_dir = tmpdir_factory.mktemp(basename="TestWorkingDir") - yield str(work_dir) diff --git a/test/testdata/test_remove/repos.txt b/test/testdata/test_remove/repos.txt new file mode 100644 index 00000000..0c9af6aa --- /dev/null +++ b/test/testdata/test_remove/repos.txt @@ -0,0 +1,4 @@ +https://github.com/arduino-libraries/ArduinoCloudThing.git|Arduino,Retired|ArduinoCloudThing +https://github.com/arduino-libraries/ArduinoIoTCloudBearSSL.git|Partner|ArduinoIoTCloudBearSSL +https://github.com/arduino-libraries/SpacebrewYun.git|Contributed|SpacebrewYun +https://github.com/arduino-libraries/UnoWiFi-Developer-Edition-Lib.git|Arduino,Retired|Arduino Uno WiFi Dev Ed Library diff --git a/test/testdata/test_remove/test_remove_basic/repos.txt b/test/testdata/test_remove/test_remove_basic/repos.txt new file mode 100644 index 00000000..eb53271b --- /dev/null +++ b/test/testdata/test_remove/test_remove_basic/repos.txt @@ -0,0 +1 @@ +https://github.com/arduino-libraries/SpacebrewYun.git|Contributed|SpacebrewYun diff --git a/test/testdata/test_all/golden/db.json b/test/testdata/test_sync/golden/db.json similarity index 100% rename from test/testdata/test_all/golden/db.json rename to test/testdata/test_sync/golden/db.json diff --git a/test/testdata/test_all/golden/library_index.json b/test/testdata/test_sync/golden/library_index.json similarity index 100% rename from test/testdata/test_all/golden/library_index.json rename to test/testdata/test_sync/golden/library_index.json diff --git a/test/testdata/test_all/golden/logs/generate/github.com/arduino-libraries/ArduinoCloudThing/index.html b/test/testdata/test_sync/golden/logs/generate/github.com/arduino-libraries/ArduinoCloudThing/index.html similarity index 100% rename from test/testdata/test_all/golden/logs/generate/github.com/arduino-libraries/ArduinoCloudThing/index.html rename to test/testdata/test_sync/golden/logs/generate/github.com/arduino-libraries/ArduinoCloudThing/index.html diff --git a/test/testdata/test_all/golden/logs/generate/github.com/arduino-libraries/SpacebrewYun/index.html b/test/testdata/test_sync/golden/logs/generate/github.com/arduino-libraries/SpacebrewYun/index.html similarity index 100% rename from test/testdata/test_all/golden/logs/generate/github.com/arduino-libraries/SpacebrewYun/index.html rename to test/testdata/test_sync/golden/logs/generate/github.com/arduino-libraries/SpacebrewYun/index.html diff --git a/test/testdata/test_all/golden/logs/update/github.com/arduino-libraries/ArduinoCloudThing/index.html b/test/testdata/test_sync/golden/logs/update/github.com/arduino-libraries/ArduinoCloudThing/index.html similarity index 100% rename from test/testdata/test_all/golden/logs/update/github.com/arduino-libraries/ArduinoCloudThing/index.html rename to test/testdata/test_sync/golden/logs/update/github.com/arduino-libraries/ArduinoCloudThing/index.html diff --git a/test/testdata/test_all/golden/logs/update/github.com/arduino-libraries/SpacebrewYun/index.html b/test/testdata/test_sync/golden/logs/update/github.com/arduino-libraries/SpacebrewYun/index.html similarity index 100% rename from test/testdata/test_all/golden/logs/update/github.com/arduino-libraries/SpacebrewYun/index.html rename to test/testdata/test_sync/golden/logs/update/github.com/arduino-libraries/SpacebrewYun/index.html diff --git a/test/testdata/test_all/repos.txt b/test/testdata/test_sync/repos.txt similarity index 100% rename from test/testdata/test_all/repos.txt rename to test/testdata/test_sync/repos.txt