github.com/rigado/snapd@v2.42.5-go-mod+incompatible/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 . "gopkg.in/check.v1" 35 36 "golang.org/x/crypto/ssh/terminal" 37 38 "github.com/snapcore/snapd/cmd" 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 snapdsnap "github.com/snapcore/snapd/snap" 44 "github.com/snapcore/snapd/testutil" 45 46 snap "github.com/snapcore/snapd/cmd/snap" 47 ) 48 49 // Hook up check.v1 into the "go test" runner 50 func Test(t *testing.T) { TestingT(t) } 51 52 type BaseSnapSuite struct { 53 testutil.BaseTest 54 stdin *bytes.Buffer 55 stdout *bytes.Buffer 56 stderr *bytes.Buffer 57 password string 58 59 AuthFile string 60 } 61 62 func (s *BaseSnapSuite) readPassword(fd int) ([]byte, error) { 63 return []byte(s.password), nil 64 } 65 66 func (s *BaseSnapSuite) SetUpTest(c *C) { 67 s.BaseTest.SetUpTest(c) 68 dirs.SetRootDir(c.MkDir()) 69 70 path := os.Getenv("PATH") 71 s.AddCleanup(func() { 72 os.Setenv("PATH", path) 73 }) 74 os.Setenv("PATH", path+":"+dirs.SnapBinariesDir) 75 76 s.stdin = bytes.NewBuffer(nil) 77 s.stdout = bytes.NewBuffer(nil) 78 s.stderr = bytes.NewBuffer(nil) 79 s.password = "" 80 81 snap.Stdin = s.stdin 82 snap.Stdout = s.stdout 83 snap.Stderr = s.stderr 84 snap.ReadPassword = s.readPassword 85 s.AuthFile = filepath.Join(c.MkDir(), "json") 86 os.Setenv(TestAuthFileEnvKey, s.AuthFile) 87 88 s.AddCleanup(snapdsnap.MockSanitizePlugsSlots(func(snapInfo *snapdsnap.Info) {})) 89 90 s.AddCleanup(interfaces.MockSystemKey(` 91 { 92 "build-id": "7a94e9736c091b3984bd63f5aebfc883c4d859e0", 93 "apparmor-features": ["caps", "dbus"] 94 }`)) 95 err := os.MkdirAll(filepath.Dir(dirs.SnapSystemKeyFile), 0755) 96 c.Assert(err, IsNil) 97 err = interfaces.WriteSystemKey() 98 c.Assert(err, IsNil) 99 100 s.AddCleanup(snap.MockIsStdoutTTY(false)) 101 s.AddCleanup(snap.MockIsStdinTTY(false)) 102 103 s.AddCleanup(snap.MockSELinuxIsEnabled(func() (bool, error) { return false, nil })) 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 mockVersion(v string) (restore func()) { 181 old := cmd.Version 182 cmd.Version = v 183 return func() { cmd.Version = old } 184 } 185 186 func mockSnapConfine(libExecDir string) func() { 187 snapConfine := filepath.Join(libExecDir, "snap-confine") 188 if err := os.MkdirAll(libExecDir, 0755); err != nil { 189 panic(err) 190 } 191 if err := ioutil.WriteFile(snapConfine, nil, 0644); err != nil { 192 panic(err) 193 } 194 return func() { 195 if err := os.Remove(snapConfine); err != nil { 196 panic(err) 197 } 198 } 199 } 200 201 const TestAuthFileEnvKey = "SNAPD_AUTH_DATA_FILENAME" 202 const TestAuthFileContents = `{"id":123,"email":"hello@mail.com","macaroon":"MDAxM2xvY2F0aW9uIHNuYXBkCjAwMTJpZGVudGlmaWVyIDQzCjAwMmZzaWduYXR1cmUg5RfMua72uYop4t3cPOBmGUuaoRmoDH1HV62nMJq7eqAK"}` 203 204 func (s *SnapSuite) TestErrorResult(c *C) { 205 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 206 fmt.Fprintln(w, `{"type": "error", "result": {"message": "cannot do something"}}`) 207 }) 208 209 restore := mockArgs("snap", "install", "foo") 210 defer restore() 211 212 err := snap.RunMain() 213 c.Assert(err, ErrorMatches, `cannot do something`) 214 } 215 216 func (s *SnapSuite) TestAccessDeniedHint(c *C) { 217 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 218 fmt.Fprintln(w, `{"type": "error", "result": {"message": "access denied", "kind": "login-required"}, "status-code": 401}`) 219 }) 220 221 restore := mockArgs("snap", "install", "foo") 222 defer restore() 223 224 err := snap.RunMain() 225 c.Assert(err, NotNil) 226 c.Check(err.Error(), Equals, `access denied (try with sudo)`) 227 } 228 229 func (s *SnapSuite) TestExtraArgs(c *C) { 230 restore := mockArgs("snap", "abort", "1", "xxx", "zzz") 231 defer restore() 232 233 err := snap.RunMain() 234 c.Assert(err, ErrorMatches, `too many arguments for command`) 235 } 236 237 func (s *SnapSuite) TestVersionOnClassic(c *C) { 238 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 239 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"}}`) 240 }) 241 restore := mockArgs("snap", "--version") 242 defer restore() 243 restore = mockVersion("4.56") 244 defer restore() 245 246 c.Assert(func() { snap.RunMain() }, PanicMatches, `internal error: exitStatus\{0\} .*`) 247 c.Assert(s.Stdout(), Equals, "snap 4.56\nsnapd 7.89\nseries 56\nubuntu 12.34\n") 248 c.Assert(s.Stderr(), Equals, "") 249 } 250 251 func (s *SnapSuite) TestVersionOnAllSnap(c *C) { 252 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 253 fmt.Fprintln(w, `{"type":"sync","status-code":200,"status":"OK","result":{"os-release":{"id":"ubuntu","version-id":"12.34"},"series":"56","version":"7.89"}}`) 254 }) 255 restore := mockArgs("snap", "--version") 256 defer restore() 257 restore = mockVersion("4.56") 258 defer restore() 259 260 c.Assert(func() { snap.RunMain() }, PanicMatches, `internal error: exitStatus\{0\} .*`) 261 c.Assert(s.Stdout(), Equals, "snap 4.56\nsnapd 7.89\nseries 56\n") 262 c.Assert(s.Stderr(), Equals, "") 263 } 264 265 func (s *SnapSuite) TestUnknownCommand(c *C) { 266 restore := mockArgs("snap", "unknowncmd") 267 defer restore() 268 269 err := snap.RunMain() 270 c.Assert(err, ErrorMatches, `unknown command "unknowncmd", see 'snap help'.`) 271 } 272 273 func (s *SnapSuite) TestResolveApp(c *C) { 274 err := os.MkdirAll(dirs.SnapBinariesDir, 0755) 275 c.Assert(err, IsNil) 276 277 // "wrapper" symlinks 278 err = os.Symlink("/usr/bin/snap", filepath.Join(dirs.SnapBinariesDir, "foo")) 279 c.Assert(err, IsNil) 280 err = os.Symlink("/usr/bin/snap", filepath.Join(dirs.SnapBinariesDir, "foo.bar")) 281 c.Assert(err, IsNil) 282 283 // alias symlinks 284 err = os.Symlink("foo", filepath.Join(dirs.SnapBinariesDir, "foo_")) 285 c.Assert(err, IsNil) 286 err = os.Symlink("foo.bar", filepath.Join(dirs.SnapBinariesDir, "foo_bar-1")) 287 c.Assert(err, IsNil) 288 289 snapApp, err := snap.ResolveApp("foo") 290 c.Assert(err, IsNil) 291 c.Check(snapApp, Equals, "foo") 292 293 snapApp, err = snap.ResolveApp("foo.bar") 294 c.Assert(err, IsNil) 295 c.Check(snapApp, Equals, "foo.bar") 296 297 snapApp, err = snap.ResolveApp("foo_") 298 c.Assert(err, IsNil) 299 c.Check(snapApp, Equals, "foo") 300 301 snapApp, err = snap.ResolveApp("foo_bar-1") 302 c.Assert(err, IsNil) 303 c.Check(snapApp, Equals, "foo.bar") 304 305 _, err = snap.ResolveApp("baz") 306 c.Check(err, NotNil) 307 } 308 309 func (s *SnapSuite) TestFirstNonOptionIsRun(c *C) { 310 osArgs := os.Args 311 defer func() { 312 os.Args = osArgs 313 }() 314 for _, negative := range []string{ 315 "", 316 "snap", 317 "snap verb", 318 "snap verb --flag arg", 319 "snap verb arg --flag", 320 "snap --global verb --flag arg", 321 } { 322 os.Args = strings.Fields(negative) 323 c.Check(snap.FirstNonOptionIsRun(), Equals, false) 324 } 325 326 for _, positive := range []string{ 327 "snap run", 328 "snap run --flag", 329 "snap run --flag arg", 330 "snap run arg --flag", 331 "snap --global run", 332 "snap --global run --flag", 333 "snap --global run --flag arg", 334 "snap --global run arg --flag", 335 } { 336 os.Args = strings.Fields(positive) 337 c.Check(snap.FirstNonOptionIsRun(), Equals, true) 338 } 339 } 340 341 func (s *SnapSuite) TestLintDesc(c *C) { 342 log, restore := logger.MockLogger() 343 defer restore() 344 345 // LintDesc is happy about capitalized description. 346 snap.LintDesc("command", "<option>", "Description ...", "") 347 c.Check(log.String(), HasLen, 0) 348 log.Reset() 349 350 // LintDesc complains about lowercase description. 351 snap.LintDesc("command", "<option>", "description", "") 352 c.Check(log.String(), testutil.Contains, `description of command's "<option>" is lowercase: "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 }