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