243 lines
5.8 KiB
Go
243 lines
5.8 KiB
Go
package updates
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"path/filepath"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestNormalizeVersion(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
version, err := NormalizeVersion("1.2.3")
|
|
if err != nil {
|
|
t.Fatalf("NormalizeVersion returned error: %v", err)
|
|
}
|
|
if version != "v1.2.3" {
|
|
t.Fatalf("NormalizeVersion returned %q", version)
|
|
}
|
|
}
|
|
|
|
func TestControllerCheckAndDownload(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
platform := &fakePlatform{
|
|
root: InstallRoot{Path: t.TempDir()},
|
|
stage: StagedArtifact{
|
|
Path: t.TempDir(),
|
|
Root: InstallRoot{Path: t.TempDir()},
|
|
Bundle: BundleManifest{
|
|
SchemaVersion: 1,
|
|
EntryPoint: "App",
|
|
Files: []BundleFile{{Path: "App", Mode: "0755"}},
|
|
},
|
|
},
|
|
}
|
|
release := &Release{
|
|
ID: "1.1.0",
|
|
Version: "1.1.0",
|
|
Channel: ChannelStable,
|
|
Artifact: Artifact{
|
|
Kind: ArtifactKindBundleArchive,
|
|
Format: ArtifactFormatZip,
|
|
URL: "https://example.invalid/app.zip",
|
|
SHA256: "deadbeef",
|
|
},
|
|
}
|
|
|
|
controller, err := NewController(Config{
|
|
App: AppDescriptor{
|
|
ProductID: "com.example.app",
|
|
CurrentVersion: "1.0.0",
|
|
Channel: ChannelStable,
|
|
OS: "linux",
|
|
Arch: "amd64",
|
|
ExecutablePath: filepath.Join(t.TempDir(), "App"),
|
|
},
|
|
Provider: fakeProvider{release: release},
|
|
Downloader: fakeDownloader{file: DownloadedFile{Path: "bundle.zip", SHA256: "deadbeef"}},
|
|
Store: &memoryStore{},
|
|
Platform: platform,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewController returned error: %v", err)
|
|
}
|
|
|
|
snapshot, err := controller.Check(context.Background(), CheckRequest{})
|
|
if err != nil {
|
|
t.Fatalf("Check returned error: %v", err)
|
|
}
|
|
if snapshot.State != StateUpdateAvailable {
|
|
t.Fatalf("Check state = %s", snapshot.State)
|
|
}
|
|
|
|
snapshot, err = controller.Download(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("Download returned error: %v", err)
|
|
}
|
|
if snapshot.State != StateReadyToApply {
|
|
t.Fatalf("Download state = %s", snapshot.State)
|
|
}
|
|
if snapshot.Staged == nil {
|
|
t.Fatal("Download did not stage artifact")
|
|
}
|
|
}
|
|
|
|
func TestControllerBusy(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
block := make(chan struct{})
|
|
controller, err := NewController(Config{
|
|
App: AppDescriptor{
|
|
ProductID: "com.example.app",
|
|
CurrentVersion: "1.0.0",
|
|
Channel: ChannelStable,
|
|
OS: "linux",
|
|
Arch: "amd64",
|
|
ExecutablePath: filepath.Join(t.TempDir(), "App"),
|
|
},
|
|
Provider: fakeProvider{
|
|
resolveFunc: func(context.Context, ResolveRequest) (*Release, error) {
|
|
<-block
|
|
return nil, nil
|
|
},
|
|
},
|
|
Downloader: fakeDownloader{},
|
|
Store: &memoryStore{},
|
|
Platform: &fakePlatform{root: InstallRoot{Path: t.TempDir()}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewController returned error: %v", err)
|
|
}
|
|
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
_, err := controller.Check(context.Background(), CheckRequest{})
|
|
done <- err
|
|
}()
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
if _, err := controller.Check(context.Background(), CheckRequest{}); !errors.Is(err, ErrBusy) {
|
|
t.Fatalf("expected ErrBusy, got %v", err)
|
|
}
|
|
close(block)
|
|
if err := <-done; err != nil {
|
|
t.Fatalf("first Check returned error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestControllerCleansStagedUpdateOnMatchingVersion(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
store := &memoryStore{}
|
|
err := store.Save(context.Background(), Snapshot{
|
|
State: StateRestarting,
|
|
Staged: &StagedArtifact{
|
|
Release: Release{Version: "1.2.0"},
|
|
},
|
|
Candidate: &Release{Version: "1.2.0"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Save returned error: %v", err)
|
|
}
|
|
|
|
controller, err := NewController(Config{
|
|
App: AppDescriptor{
|
|
ProductID: "com.example.app",
|
|
CurrentVersion: "1.2.0",
|
|
Channel: ChannelStable,
|
|
OS: "linux",
|
|
Arch: "amd64",
|
|
ExecutablePath: filepath.Join(t.TempDir(), "App"),
|
|
},
|
|
Provider: fakeProvider{},
|
|
Downloader: fakeDownloader{},
|
|
Store: store,
|
|
Platform: &fakePlatform{root: InstallRoot{Path: t.TempDir()}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewController returned error: %v", err)
|
|
}
|
|
|
|
snapshot := controller.Snapshot()
|
|
if snapshot.Staged != nil {
|
|
t.Fatal("expected staged update to be cleared")
|
|
}
|
|
if snapshot.State != StateUpToDate {
|
|
t.Fatalf("snapshot state = %s", snapshot.State)
|
|
}
|
|
}
|
|
|
|
type fakeProvider struct {
|
|
release *Release
|
|
resolveFunc func(context.Context, ResolveRequest) (*Release, error)
|
|
}
|
|
|
|
func (provider fakeProvider) Resolve(ctx context.Context, req ResolveRequest) (*Release, error) {
|
|
if provider.resolveFunc != nil {
|
|
return provider.resolveFunc(ctx, req)
|
|
}
|
|
return provider.release, nil
|
|
}
|
|
|
|
func (provider fakeProvider) OpenArtifact(context.Context, Release) (io.ReadCloser, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
type fakeDownloader struct {
|
|
file DownloadedFile
|
|
err error
|
|
}
|
|
|
|
func (downloader fakeDownloader) Download(context.Context, Artifact) (DownloadedFile, error) {
|
|
return downloader.file, downloader.err
|
|
}
|
|
|
|
type fakePlatform struct {
|
|
root InstallRoot
|
|
stage StagedArtifact
|
|
err error
|
|
}
|
|
|
|
func (platform *fakePlatform) DetectInstallRoot(AppDescriptor) (InstallRoot, error) {
|
|
return platform.root, platform.err
|
|
}
|
|
|
|
func (platform *fakePlatform) Stage(context.Context, InstallRoot, DownloadedFile, Release) (StagedArtifact, error) {
|
|
return platform.stage, platform.err
|
|
}
|
|
|
|
func (platform *fakePlatform) SpawnApplyAndRestart(context.Context, ApplyRequest) error {
|
|
return platform.err
|
|
}
|
|
|
|
type memoryStore struct {
|
|
mu sync.Mutex
|
|
snapshot Snapshot
|
|
}
|
|
|
|
func (store *memoryStore) Load(context.Context) (Snapshot, error) {
|
|
store.mu.Lock()
|
|
defer store.mu.Unlock()
|
|
return store.snapshot, nil
|
|
}
|
|
|
|
func (store *memoryStore) Save(_ context.Context, snapshot Snapshot) error {
|
|
store.mu.Lock()
|
|
defer store.mu.Unlock()
|
|
store.snapshot = snapshot
|
|
return nil
|
|
}
|
|
|
|
func (store *memoryStore) ClearStaged(context.Context) error {
|
|
store.mu.Lock()
|
|
defer store.mu.Unlock()
|
|
store.snapshot.Staged = nil
|
|
store.snapshot.Candidate = nil
|
|
return nil
|
|
}
|