github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/cmd/snap/main_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package main_test 21 22 import ( 23 "bytes" 24 "encoding/json" 25 "fmt" 26 "io/ioutil" 27 "net/http" 28 "net/http/httptest" 29 "os" 30 "path/filepath" 31 "strings" 32 "testing" 33 34 "github.com/jessevdk/go-flags" 35 "golang.org/x/crypto/ssh/terminal" 36 . "gopkg.in/check.v1" 37 38 "github.com/snapcore/snapd/dirs" 39 "github.com/snapcore/snapd/interfaces" 40 "github.com/snapcore/snapd/logger" 41 "github.com/snapcore/snapd/osutil" 42 "github.com/snapcore/snapd/snapdtool" 43 "github.com/snapcore/snapd/testutil" 44 45 snap "github.com/snapcore/snapd/cmd/snap" 46 ) 47 48 // Hook up check.v1 into the "go test" runner 49 func Test(t *testing.T) { TestingT(t) } 50 51 type BaseSnapSuite struct { 52 testutil.BaseTest 53 stdin *bytes.Buffer 54 stdout *bytes.Buffer 55 stderr *bytes.Buffer 56 password string 57 58 AuthFile string 59 } 60 61 func (s *BaseSnapSuite) readPassword(fd int) ([]byte, error) { 62 return []byte(s.password), nil 63 } 64 65 func (s *BaseSnapSuite) SetUpTest(c *C) { 66 s.BaseTest.SetUpTest(c) 67 dirs.SetRootDir(c.MkDir()) 68 69 path := os.Getenv("PATH") 70 s.AddCleanup(func() { 71 os.Setenv("PATH", path) 72 }) 73 os.Setenv("PATH", path+":"+dirs.SnapBinariesDir) 74 75 s.stdin = bytes.NewBuffer(nil) 76 s.stdout = bytes.NewBuffer(nil) 77 s.stderr = bytes.NewBuffer(nil) 78 s.password = "" 79 80 snap.Stdin = s.stdin 81 snap.Stdout = s.stdout 82 snap.Stderr = s.stderr 83 snap.ReadPassword = s.readPassword 84 s.AuthFile = filepath.Join(c.MkDir(), "json") 85 os.Setenv(TestAuthFileEnvKey, s.AuthFile) 86 87 s.AddCleanup(interfaces.MockSystemKey(` 88 { 89 "build-id": "7a94e9736c091b3984bd63f5aebfc883c4d859e0", 90 "apparmor-features": ["caps", "dbus"] 91 }`)) 92 err := os.MkdirAll(filepath.Dir(dirs.SnapSystemKeyFile), 0755) 93 c.Assert(err, IsNil) 94 err = interfaces.WriteSystemKey() 95 c.Assert(err, IsNil) 96 97 s.AddCleanup(snap.MockIsStdoutTTY(false)) 98 s.AddCleanup(snap.MockIsStdinTTY(false)) 99 100 s.AddCleanup(snap.MockSELinuxIsEnabled(func() (bool, error) { return false, nil })) 101 } 102 103 func (s *BaseSnapSuite) TearDownTest(c *C) { 104 snap.Stdin = os.Stdin 105 snap.Stdout = os.Stdout 106 snap.Stderr = os.Stderr 107 snap.ReadPassword = terminal.ReadPassword 108 109 c.Assert(s.AuthFile == "", Equals, false) 110 err := os.Unsetenv(TestAuthFileEnvKey) 111 c.Assert(err, IsNil) 112 dirs.SetRootDir("/") 113 s.BaseTest.TearDownTest(c) 114 } 115 116 func (s *BaseSnapSuite) Stdout() string { 117 return s.stdout.String() 118 } 119 120 func (s *BaseSnapSuite) Stderr() string { 121 return s.stderr.String() 122 } 123 124 func (s *BaseSnapSuite) ResetStdStreams() { 125 s.stdin.Reset() 126 s.stdout.Reset() 127 s.stderr.Reset() 128 } 129 130 func (s *BaseSnapSuite) RedirectClientToTestServer(handler func(http.ResponseWriter, *http.Request)) { 131 server := httptest.NewServer(http.HandlerFunc(handler)) 132 s.BaseTest.AddCleanup(func() { server.Close() }) 133 snap.ClientConfig.BaseURL = server.URL 134 s.BaseTest.AddCleanup(func() { snap.ClientConfig.BaseURL = "" }) 135 } 136 137 func (s *BaseSnapSuite) Login(c *C) { 138 err := osutil.AtomicWriteFile(s.AuthFile, []byte(TestAuthFileContents), 0600, 0) 139 c.Assert(err, IsNil) 140 } 141 142 func (s *BaseSnapSuite) Logout(c *C) { 143 if osutil.FileExists(s.AuthFile) { 144 c.Assert(os.Remove(s.AuthFile), IsNil) 145 } 146 } 147 148 type SnapSuite struct { 149 BaseSnapSuite 150 } 151 152 var _ = Suite(&SnapSuite{}) 153 154 // DecodedRequestBody returns the JSON-decoded body of the request. 155 func DecodedRequestBody(c *C, r *http.Request) map[string]interface{} { 156 var body map[string]interface{} 157 decoder := json.NewDecoder(r.Body) 158 decoder.UseNumber() 159 err := decoder.Decode(&body) 160 c.Assert(err, IsNil) 161 return body 162 } 163 164 // EncodeResponseBody writes JSON-serialized body to the response writer. 165 func EncodeResponseBody(c *C, w http.ResponseWriter, body interface{}) { 166 encoder := json.NewEncoder(w) 167 err := encoder.Encode(body) 168 c.Assert(err, IsNil) 169 } 170 171 func mockArgs(args ...string) (restore func()) { 172 old := os.Args 173 os.Args = args 174 return func() { os.Args = old } 175 } 176 177 func mockSnapConfine(libExecDir string) func() { 178 snapConfine := filepath.Join(libExecDir, "snap-confine") 179 if err := os.MkdirAll(libExecDir, 0755); err != nil { 180 panic(err) 181 } 182 if err := ioutil.WriteFile(snapConfine, nil, 0644); err != nil { 183 panic(err) 184 } 185 return func() { 186 if err := os.Remove(snapConfine); err != nil { 187 panic(err) 188 } 189 } 190 } 191 192 const TestAuthFileEnvKey = "SNAPD_AUTH_DATA_FILENAME" 193 const TestAuthFileContents = `{"id":123,"email":"hello@mail.com","macaroon":"MDAxM2xvY2F0aW9uIHNuYXBkCjAwMTJpZGVudGlmaWVyIDQzCjAwMmZzaWduYXR1cmUg5RfMua72uYop4t3cPOBmGUuaoRmoDH1HV62nMJq7eqAK"}` 194 195 func (s *SnapSuite) TestErrorResult(c *C) { 196 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 197 fmt.Fprintln(w, `{"type": "error", "result": {"message": "cannot do something"}}`) 198 }) 199 200 restore := mockArgs("snap", "install", "foo") 201 defer restore() 202 203 err := snap.RunMain() 204 c.Assert(err, ErrorMatches, `cannot do something`) 205 } 206 207 func (s *SnapSuite) TestAccessDeniedHint(c *C) { 208 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 209 fmt.Fprintln(w, `{"type": "error", "result": {"message": "access denied", "kind": "login-required"}, "status-code": 401}`) 210 }) 211 212 restore := mockArgs("snap", "install", "foo") 213 defer restore() 214 215 err := snap.RunMain() 216 c.Assert(err, NotNil) 217 c.Check(err.Error(), Equals, `access denied (try with sudo)`) 218 } 219 220 func (s *SnapSuite) TestExtraArgs(c *C) { 221 restore := mockArgs("snap", "abort", "1", "xxx", "zzz") 222 defer restore() 223 224 err := snap.RunMain() 225 c.Assert(err, ErrorMatches, `too many arguments for command`) 226 } 227 228 func (s *SnapSuite) TestVersionOnClassic(c *C) { 229 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 230 fmt.Fprintln(w, `{"type":"sync","status-code":200,"status":"OK","result":{"on-classic":true,"os-release":{"id":"ubuntu","version-id":"12.34"},"series":"56","version":"7.89"}}`) 231 }) 232 restore := mockArgs("snap", "--version") 233 defer restore() 234 restore = snapdtool.MockVersion("4.56") 235 defer restore() 236 237 c.Assert(func() { snap.RunMain() }, PanicMatches, `internal error: exitStatus\{0\} .*`) 238 c.Assert(s.Stdout(), Equals, "snap 4.56\nsnapd 7.89\nseries 56\nubuntu 12.34\n") 239 c.Assert(s.Stderr(), Equals, "") 240 } 241 242 func (s *SnapSuite) TestVersionOnAllSnap(c *C) { 243 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 244 fmt.Fprintln(w, `{"type":"sync","status-code":200,"status":"OK","result":{"os-release":{"id":"ubuntu","version-id":"12.34"},"series":"56","version":"7.89"}}`) 245 }) 246 restore := mockArgs("snap", "--version") 247 defer restore() 248 restore = snapdtool.MockVersion("4.56") 249 defer restore() 250 251 c.Assert(func() { snap.RunMain() }, PanicMatches, `internal error: exitStatus\{0\} .*`) 252 c.Assert(s.Stdout(), Equals, "snap 4.56\nsnapd 7.89\nseries 56\n") 253 c.Assert(s.Stderr(), Equals, "") 254 } 255 256 func (s *SnapSuite) TestUnknownCommand(c *C) { 257 restore := mockArgs("snap", "unknowncmd") 258 defer restore() 259 260 err := snap.RunMain() 261 c.Assert(err, ErrorMatches, `unknown command "unknowncmd", see 'snap help'.`) 262 } 263 264 func (s *SnapSuite) TestResolveApp(c *C) { 265 err := os.MkdirAll(dirs.SnapBinariesDir, 0755) 266 c.Assert(err, IsNil) 267 268 // "wrapper" symlinks 269 err = os.Symlink("/usr/bin/snap", filepath.Join(dirs.SnapBinariesDir, "foo")) 270 c.Assert(err, IsNil) 271 err = os.Symlink("/usr/bin/snap", filepath.Join(dirs.SnapBinariesDir, "foo.bar")) 272 c.Assert(err, IsNil) 273 274 // alias symlinks 275 err = os.Symlink("foo", filepath.Join(dirs.SnapBinariesDir, "foo_")) 276 c.Assert(err, IsNil) 277 err = os.Symlink("foo.bar", filepath.Join(dirs.SnapBinariesDir, "foo_bar-1")) 278 c.Assert(err, IsNil) 279 280 snapApp, err := snap.ResolveApp("foo") 281 c.Assert(err, IsNil) 282 c.Check(snapApp, Equals, "foo") 283 284 snapApp, err = snap.ResolveApp("foo.bar") 285 c.Assert(err, IsNil) 286 c.Check(snapApp, Equals, "foo.bar") 287 288 snapApp, err = snap.ResolveApp("foo_") 289 c.Assert(err, IsNil) 290 c.Check(snapApp, Equals, "foo") 291 292 snapApp, err = snap.ResolveApp("foo_bar-1") 293 c.Assert(err, IsNil) 294 c.Check(snapApp, Equals, "foo.bar") 295 296 _, err = snap.ResolveApp("baz") 297 c.Check(err, NotNil) 298 } 299 300 func (s *SnapSuite) TestFirstNonOptionIsRun(c *C) { 301 osArgs := os.Args 302 defer func() { 303 os.Args = osArgs 304 }() 305 for _, negative := range []string{ 306 "", 307 "snap", 308 "snap verb", 309 "snap verb --flag arg", 310 "snap verb arg --flag", 311 "snap --global verb --flag arg", 312 } { 313 os.Args = strings.Fields(negative) 314 c.Check(snap.FirstNonOptionIsRun(), Equals, false) 315 } 316 317 for _, positive := range []string{ 318 "snap run", 319 "snap run --flag", 320 "snap run --flag arg", 321 "snap run arg --flag", 322 "snap --global run", 323 "snap --global run --flag", 324 "snap --global run --flag arg", 325 "snap --global run arg --flag", 326 } { 327 os.Args = strings.Fields(positive) 328 c.Check(snap.FirstNonOptionIsRun(), Equals, true) 329 } 330 } 331 332 func (s *SnapSuite) TestLintDesc(c *C) { 333 log, restore := logger.MockLogger() 334 defer restore() 335 336 // LintDesc is happy about capitalized description. 337 snap.LintDesc("command", "<option>", "Description ...", "") 338 c.Check(log.String(), HasLen, 0) 339 log.Reset() 340 341 // LintDesc complains about lowercase description. 342 snap.LintDesc("command", "<option>", "description", "") 343 c.Check(log.String(), testutil.Contains, `description of command's "<option>" is lowercase: "description"`) 344 log.Reset() 345 346 // LintDesc does not complain about lowercase description starting with login.ubuntu.com 347 snap.LintDesc("command", "<option>", "login.ubuntu.com description", "") 348 c.Check(log.String(), HasLen, 0) 349 log.Reset() 350 351 // LintDesc panics when original description is present. 352 fn := func() { 353 snap.LintDesc("command", "<option>", "description", "original description") 354 } 355 c.Check(fn, PanicMatches, `description of command's "<option>" of "original description" set from tag \(=> no i18n\)`) 356 log.Reset() 357 358 // LintDesc panics when option name is empty. 359 fn = func() { 360 snap.LintDesc("command", "", "description", "") 361 } 362 c.Check(fn, PanicMatches, `option on "command" has no name`) 363 log.Reset() 364 365 snap.LintDesc("snap-advise", "from-apt", "snap-advise will run as a hook", "") 366 c.Check(log.String(), HasLen, 0) 367 log.Reset() 368 } 369 370 func (s *SnapSuite) TestLintArg(c *C) { 371 log, restore := logger.MockLogger() 372 defer restore() 373 374 // LintArg is happy when option is enclosed with < >. 375 snap.LintArg("command", "<option>", "Description", "") 376 c.Check(log.String(), HasLen, 0) 377 log.Reset() 378 379 // LintArg complains about when option is not properly enclosed with < >. 380 snap.LintArg("command", "option", "Description", "") 381 c.Check(log.String(), testutil.Contains, `argument "command"'s "option" should begin with < and end with >`) 382 log.Reset() 383 snap.LintArg("command", "<option", "Description", "") 384 c.Check(log.String(), testutil.Contains, `argument "command"'s "<option" should begin with < and end with >`) 385 log.Reset() 386 snap.LintArg("command", "option>", "Description", "") 387 c.Check(log.String(), testutil.Contains, `argument "command"'s "option>" should begin with < and end with >`) 388 log.Reset() 389 390 // LintArg ignores the special case of <option>s. 391 snap.LintArg("command", "<option>s", "Description", "") 392 c.Check(log.String(), HasLen, 0) 393 log.Reset() 394 } 395 396 func (s *SnapSuite) TestFixupArg(c *C) { 397 c.Check(snap.FixupArg("option"), Equals, "option") 398 c.Check(snap.FixupArg("<option>"), Equals, "<option>") 399 // Trailing ">s" is fixed to just >. 400 c.Check(snap.FixupArg("<option>s"), Equals, "<option>") 401 } 402 403 func (s *SnapSuite) TestSetsUserAgent(c *C) { 404 testServerHit := false 405 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 406 c.Check(r.Header.Get("User-Agent"), Matches, "snapd/.*") 407 testServerHit = true 408 409 fmt.Fprintln(w, `{"type": "error", "result": {"message": "cannot do something"}}`) 410 }) 411 restore := mockArgs("snap", "install", "foo") 412 defer restore() 413 414 _ = snap.RunMain() 415 c.Assert(testServerHit, Equals, true) 416 } 417 418 func (s *SnapSuite) TestCompletionHandlerSkipsHidden(c *C) { 419 snap.MarkForNoCompletion(snap.HiddenCmd("bar yadda yack", false)) 420 snap.MarkForNoCompletion(snap.HiddenCmd("bar yack yack yack", true)) 421 snap.CompletionHandler([]flags.Completion{ 422 {Item: "foo", Description: "foo yadda yadda"}, 423 {Item: "bar", Description: "bar yadda yack"}, 424 {Item: "baz", Description: "bar yack yack yack"}, 425 }) 426 c.Check(s.Stdout(), Equals, "foo\nbaz\n") 427 }