Browse Source

cmd/fdroidcl: rewrite tests to not use go build

This way, we avoid linking an executable, and running the tests is also
faster. This shaves the test time by half, from ~9s to ~4s.

This also means we can use tooling better. For example, the cover tool
now works properly, reporting a coverage of nearly 60%.
pull/32/head
Daniel Martí 8 years ago
parent
commit
78a3907b03
  1. 2
      cmd/fdroidcl/devices.go
  2. 2
      cmd/fdroidcl/download.go
  3. 83
      cmd/fdroidcl/endtoend_test.go
  4. 2
      cmd/fdroidcl/install.go
  5. 2
      cmd/fdroidcl/list.go
  6. 48
      cmd/fdroidcl/main.go
  7. 6
      cmd/fdroidcl/search.go
  8. 17
      cmd/fdroidcl/show.go
  9. 2
      cmd/fdroidcl/uninstall.go
  10. 8
      cmd/fdroidcl/update.go
  11. 2
      cmd/fdroidcl/upgrade.go

2
cmd/fdroidcl/devices.go

@ -27,7 +27,7 @@ func runDevices(args []string) error {
return fmt.Errorf("could not get devices: %v", err)
}
for _, device := range devices {
fmt.Printf("%s - %s (%s)\n", device.ID, device.Model, device.Product)
fmt.Fprintf(stdout, "%s - %s (%s)\n", device.ID, device.Model, device.Product)
}
return nil
}

2
cmd/fdroidcl/download.go

@ -40,7 +40,7 @@ func runDownload(args []string) error {
if err != nil {
return err
}
fmt.Printf("APK available in %s\n", path)
fmt.Fprintf(stdout, "APK available in %s\n", path)
}
return nil
}

83
cmd/fdroidcl/endtoend_test.go

@ -8,11 +8,11 @@ import (
"io/ioutil"
"net/http"
"os"
"os/exec"
"path/filepath"
"regexp"
"testing"
"time"
"mvdan.cc/fdroidcl/adb"
)
// chosenApp is the app that will be installed and uninstalled on a connected
@ -23,7 +23,7 @@ import (
// any data.
const chosenApp = "org.vi_server.red_screen"
func TestEndToEnd(t *testing.T) {
func TestCommands(t *testing.T) {
url := config.Repos[0].URL
client := http.Client{Timeout: 2 * time.Second}
if _, err := client.Get(url); err != nil {
@ -35,72 +35,62 @@ func TestEndToEnd(t *testing.T) {
t.Fatal(err)
}
defer os.RemoveAll(dir)
testBasedir = dir
// Build fdroidcl in the temporary directory.
fdroidcl := filepath.Join(dir, "fdroidcl")
if out, err := exec.Command("go", "build",
"-ldflags=-X main.testBasedir="+dir,
"-o", fdroidcl).CombinedOutput(); err != nil {
t.Fatalf("%s", out)
}
mustSucceed := func(t *testing.T, want string, args ...string) {
mustRun(t, true, want, fdroidcl, args...)
mustSucceed := func(t *testing.T, want string, cmd *Command, args ...string) {
mustRun(t, true, want, cmd, args...)
}
mustFail := func(t *testing.T, want string, args ...string) {
mustRun(t, false, want, fdroidcl, args...)
mustFail := func(t *testing.T, want string, cmd *Command, args ...string) {
mustRun(t, false, want, cmd, args...)
}
t.Run("Help", func(t *testing.T) {
mustFail(t, `Usage: fdroidcl`, "-h")
})
t.Run("UnknownCommand", func(t *testing.T) {
mustFail(t, `Unrecognised command`, "unknown")
})
t.Run("Version", func(t *testing.T) {
mustSucceed(t, `^v`, "version")
mustSucceed(t, `^v`, cmdVersion)
})
t.Run("SearchBeforeUpdate", func(t *testing.T) {
mustFail(t, `could not open index`, "search")
mustFail(t, `could not open index`, cmdSearch)
})
t.Run("UpdateFirst", func(t *testing.T) {
mustSucceed(t, `done`, "update")
mustSucceed(t, `done`, cmdUpdate)
})
t.Run("UpdateCached", func(t *testing.T) {
mustSucceed(t, `not modified`, "update")
mustSucceed(t, `not modified`, cmdUpdate)
})
t.Run("SearchNoArgs", func(t *testing.T) {
mustSucceed(t, `F-Droid`, "search")
mustSucceed(t, `F-Droid`, cmdSearch)
})
t.Run("SearchWithArgs", func(t *testing.T) {
mustSucceed(t, `F-Droid`, "search", "fdroid.fdroid")
mustSucceed(t, `F-Droid`, cmdSearch, "fdroid.fdroid")
})
t.Run("SearchWithArgsNone", func(t *testing.T) {
mustSucceed(t, `^$`, "search", "nomatches")
mustSucceed(t, `^$`, cmdSearch, "nomatches")
})
t.Run("SearchOnlyPackageNames", func(t *testing.T) {
mustSucceed(t, `^[^ ]*$`, "search", "-q", "fdroid.fdroid")
mustSucceed(t, `^[^ ]*$`, cmdSearch, "-q", "fdroid.fdroid")
})
t.Run("ShowOne", func(t *testing.T) {
mustSucceed(t, `fdroid/fdroidclient`, "show", "org.fdroid.fdroid")
mustSucceed(t, `fdroid/fdroidclient`, cmdShow, "org.fdroid.fdroid")
})
t.Run("ShowMany", func(t *testing.T) {
mustSucceed(t, `fdroid/fdroidclient.*fdroid/privileged-extension`,
"show", "org.fdroid.fdroid", "org.fdroid.fdroid.privileged")
cmdShow, "org.fdroid.fdroid", "org.fdroid.fdroid.privileged")
})
t.Run("ListCategories", func(t *testing.T) {
mustSucceed(t, `Development`, "list", "categories")
mustSucceed(t, `Development`, cmdList, "categories")
})
out, err := exec.Command(fdroidcl, "devices").CombinedOutput()
if err := startAdbIfNeeded(); err != nil {
t.Fatal(err)
}
devices, err := adb.Devices()
if err != nil {
t.Fatal(err)
}
switch bytes.Count(out, []byte("\n")) {
switch len(devices) {
case 0:
t.Log("skipping the device tests as none was found via ADB")
case 1:
@ -110,38 +100,43 @@ func TestEndToEnd(t *testing.T) {
}
// try to uninstall the app first
exec.Command(fdroidcl, "uninstall", chosenApp).Run()
devices[0].Uninstall(chosenApp)
t.Run("UninstallMissing", func(t *testing.T) {
mustFail(t, `not installed$`, "uninstall", chosenApp)
mustFail(t, `not installed$`, cmdUninstall, chosenApp)
})
t.Run("InstallVersioned", func(t *testing.T) {
mustSucceed(t, `Installing `+regexp.QuoteMeta(chosenApp),
"install", chosenApp+":1")
cmdInstall, chosenApp+":1")
})
t.Run("Upgrade", func(t *testing.T) {
mustSucceed(t, `Upgrading `+regexp.QuoteMeta(chosenApp),
"upgrade", chosenApp)
cmdUpgrade, chosenApp)
})
t.Run("UpgradeAlreadyInstalled", func(t *testing.T) {
mustFail(t, `is up to date$`, "upgrade", chosenApp)
mustFail(t, `is up to date$`, cmdUpgrade, chosenApp)
})
t.Run("UninstallExisting", func(t *testing.T) {
mustSucceed(t, `Uninstalling `+regexp.QuoteMeta(chosenApp),
"uninstall", chosenApp)
cmdUninstall, chosenApp)
})
}
func mustRun(t *testing.T, success bool, wantRe, name string, args ...string) {
cmd := exec.Command(name, args...)
out, err := cmd.CombinedOutput()
func mustRun(t *testing.T, success bool, wantRe string, cmd *Command, args ...string) {
var buf bytes.Buffer
stdout, stderr = &buf, &buf
err := cmd.Run(args)
out := buf.String()
if success && err != nil {
t.Fatalf("unexpected error: %v\n%s", err, out)
} else if !success && err == nil {
t.Fatalf("expected error, got none\n%s", out)
}
if err != nil {
out += err.Error()
}
// Let '.' match newlines, and treat the output as a single line.
wantRe = "(?sm)" + wantRe
if !regexp.MustCompile(wantRe).Match(out) {
if !regexp.MustCompile(wantRe).MatchString(out) {
t.Fatalf("output does not match %#q:\n%s", wantRe, out)
}
}

2
cmd/fdroidcl/install.go

@ -69,7 +69,7 @@ func downloadAndDo(apps []*fdroidcl.App, device *adb.Device, doApk func(*adb.Dev
}
func installApk(device *adb.Device, apk *fdroidcl.Apk, path string) error {
fmt.Printf("Installing %s\n", apk.AppID)
fmt.Fprintf(stdout, "Installing %s\n", apk.AppID)
if err := device.Install(path); err != nil {
return fmt.Errorf("could not install %s: %v", apk.AppID, err)
}

2
cmd/fdroidcl/list.go

@ -42,7 +42,7 @@ func runList(args []string) error {
}
sort.Strings(result)
for _, s := range result {
fmt.Println(s)
fmt.Fprintln(stdout, s)
}
return nil
}

48
cmd/fdroidcl/main.go

@ -7,6 +7,7 @@ import (
"encoding/json"
"flag"
"fmt"
"io"
"os"
"path/filepath"
"strings"
@ -19,7 +20,7 @@ const cmdName = "fdroidcl"
var version = "v0.3.1"
func errExit(format string, a ...interface{}) {
fmt.Fprintf(os.Stderr, format, a...)
fmt.Fprintf(stderr, format, a...)
os.Exit(1)
}
@ -31,7 +32,12 @@ func subdir(dir, name string) string {
return p
}
var testBasedir = ""
var (
stdout io.Writer = os.Stdout
stderr io.Writer = os.Stderr
testBasedir = ""
)
func mustCache() string {
if testBasedir != "" {
@ -125,11 +131,11 @@ func (c *Command) Name() string {
}
func (c *Command) Usage() {
fmt.Fprintf(os.Stderr, "Usage: %s %s [-h]\n", cmdName, c.UsageLine)
fmt.Fprintf(stderr, "Usage: %s %s [-h]\n", cmdName, c.UsageLine)
anyFlags := false
c.Flag.VisitAll(func(f *flag.Flag) { anyFlags = true })
if anyFlags {
fmt.Fprintf(os.Stderr, "\nAvailable options:\n")
fmt.Fprintf(stderr, "\nAvailable options:\n")
c.Flag.PrintDefaults()
}
os.Exit(2)
@ -137,8 +143,8 @@ func (c *Command) Usage() {
func init() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %s [-h] <command> [<args>]\n\n", cmdName)
fmt.Fprintf(os.Stderr, "Available commands:\n")
fmt.Fprintf(stderr, "Usage: %s [-h] <command> [<args>]\n\n", cmdName)
fmt.Fprintf(stderr, "Available commands:\n")
maxUsageLen := 0
for _, c := range commands {
if len(c.UsageLine) > maxUsageLen {
@ -146,11 +152,11 @@ func init() {
}
}
for _, c := range commands {
fmt.Fprintf(os.Stderr, " %s%s %s\n", c.UsageLine,
fmt.Fprintf(stderr, " %s%s %s\n", c.UsageLine,
strings.Repeat(" ", maxUsageLen-len(c.UsageLine)), c.Short)
}
fmt.Fprintf(os.Stderr, "\nA specific version of an app can be selected by following the appid with an colon (:) and the version code of the app to select.\n")
fmt.Fprintf(os.Stderr, "\nUse %s <command> -h for more info\n", cmdName)
fmt.Fprintf(stderr, "\nA specific version of an app can be selected by following the appid with an colon (:) and the version code of the app to select.\n")
fmt.Fprintf(stderr, "\nUse %s <command> -h for more info\n", cmdName)
}
}
@ -166,16 +172,18 @@ var commands = []*Command{
cmdUpgrade,
cmdUninstall,
cmdDefaults,
{
UsageLine: "version",
Short: "Print version information",
Run: func(args []string) error {
if len(args) > 0 {
return fmt.Errorf("no arguments allowed")
}
fmt.Println(version)
return nil
},
cmdVersion,
}
var cmdVersion = &Command{
UsageLine: "version",
Short: "Print version information",
Run: func(args []string) error {
if len(args) > 0 {
return fmt.Errorf("no arguments allowed")
}
fmt.Fprintln(stdout, version)
return nil
},
}
@ -205,7 +213,7 @@ func main() {
switch cmdName {
default:
fmt.Fprintf(os.Stderr, "Unrecognised command '%s'\n\n", cmdName)
fmt.Fprintf(stderr, "Unrecognised command '%s'\n\n", cmdName)
flag.Usage()
os.Exit(2)
}

6
cmd/fdroidcl/search.go

@ -77,7 +77,7 @@ func runSearch(args []string) error {
}
if *quiet {
for _, app := range apps {
fmt.Println(app.ID)
fmt.Fprintln(stdout, app.ID)
}
} else {
printApps(apps, inst, device)
@ -151,9 +151,9 @@ func descVersion(app fdroidcl.App, inst *adb.Package, device *adb.Device) string
}
func printApp(app fdroidcl.App, IDLen int, inst *adb.Package, device *adb.Device) {
fmt.Printf("%s%s %s - %s\n", app.ID, strings.Repeat(" ", IDLen-len(app.ID)),
fmt.Fprintf(stdout, "%s%s %s - %s\n", app.ID, strings.Repeat(" ", IDLen-len(app.ID)),
app.Name, descVersion(app, inst, device))
fmt.Printf(" %s\n", app.Summary)
fmt.Fprintf(stdout, " %s\n", app.Summary)
}
func filterAppsInstalled(apps []fdroidcl.App, inst map[string]adb.Package) []fdroidcl.App {

17
cmd/fdroidcl/show.go

@ -5,7 +5,6 @@ package main
import (
"fmt"
"os"
"strconv"
"strings"
@ -31,7 +30,7 @@ func runShow(args []string) error {
}
for i, app := range apps {
if i > 0 {
fmt.Printf("\n--\n\n")
fmt.Fprintf(stdout, "\n--\n\n")
}
printAppDetailed(*app)
}
@ -91,9 +90,9 @@ func findApps(ids []string) ([]*fdroidcl.App, error) {
func printAppDetailed(app fdroidcl.App) {
p := func(title string, format string, args ...interface{}) {
if format == "" {
fmt.Println(title)
fmt.Fprintln(stdout, title)
} else {
fmt.Printf("%s %s\n", title, fmt.Sprintf(format, args...))
fmt.Fprintf(stdout, "%s %s\n", title, fmt.Sprintf(format, args...))
}
}
p("Package :", "%s", app.ID)
@ -130,14 +129,14 @@ func printAppDetailed(app fdroidcl.App) {
if app.FlattrID != "" {
p("Flattr :", "https://flattr.com/thing/%s", app.FlattrID)
}
fmt.Println()
fmt.Fprintln(stdout)
p("Description :", "")
fmt.Println()
app.TextDesc(os.Stdout)
fmt.Println()
fmt.Fprintln(stdout)
app.TextDesc(stdout)
fmt.Fprintln(stdout)
p("Available Versions :", "")
for _, apk := range app.Apks {
fmt.Println()
fmt.Fprintln(stdout)
p(" Version :", "%s (%d)", apk.VName, apk.VCode)
p(" Size :", "%d", apk.Size)
p(" MinSdk :", "%d", apk.MinSdk)

2
cmd/fdroidcl/uninstall.go

@ -31,7 +31,7 @@ func runUninstall(args []string) error {
}
for _, id := range args {
var err error
fmt.Printf("Uninstalling %s\n", id)
fmt.Fprintf(stdout, "Uninstalling %s\n", id)
if _, installed := inst[id]; installed {
err = device.Uninstall(id)
} else {

8
cmd/fdroidcl/update.go

@ -78,8 +78,8 @@ func respEtag(resp *http.Response) string {
var errNotModified = fmt.Errorf("not modified")
func downloadEtag(url, path string, sum []byte) error {
fmt.Printf("Downloading %s... ", url)
defer fmt.Println()
fmt.Fprintf(stdout, "Downloading %s... ", url)
defer fmt.Fprintln(stdout)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return err
@ -102,7 +102,7 @@ func downloadEtag(url, path string, sum []byte) error {
resp.StatusCode, http.StatusText(resp.StatusCode))
}
if resp.StatusCode == http.StatusNotModified {
fmt.Printf("not modified")
fmt.Fprintf(stdout, "not modified")
return errNotModified
}
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
@ -131,7 +131,7 @@ func downloadEtag(url, path string, sum []byte) error {
if err := ioutil.WriteFile(etagPath, []byte(respEtag(resp)), 0644); err != nil {
return err
}
fmt.Printf("done")
fmt.Fprintf(stdout, "done")
return nil
}

2
cmd/fdroidcl/upgrade.go

@ -52,7 +52,7 @@ func runUpgrade(args []string) error {
}
func upgradeApk(device *adb.Device, apk *fdroidcl.Apk, path string) error {
fmt.Printf("Upgrading %s\n", apk.AppID)
fmt.Fprintf(stdout, "Upgrading %s\n", apk.AppID)
if err := device.Upgrade(path); err != nil {
return fmt.Errorf("could not upgrade %s: %v", apk.AppID, err)
}

Loading…
Cancel
Save