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