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