github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/overlord/hookstate/ctlcmd/is_connected_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019 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 ctlcmd_test 21 22 import ( 23 "fmt" 24 25 . "gopkg.in/check.v1" 26 27 "github.com/snapcore/snapd/dirs" 28 "github.com/snapcore/snapd/overlord/hookstate" 29 "github.com/snapcore/snapd/overlord/hookstate/ctlcmd" 30 "github.com/snapcore/snapd/overlord/hookstate/hooktest" 31 "github.com/snapcore/snapd/overlord/snapstate" 32 "github.com/snapcore/snapd/overlord/state" 33 "github.com/snapcore/snapd/snap" 34 "github.com/snapcore/snapd/snap/snaptest" 35 "github.com/snapcore/snapd/testutil" 36 ) 37 38 type isConnectedSuite struct { 39 testutil.BaseTest 40 st *state.State 41 mockHandler *hooktest.MockHandler 42 } 43 44 var _ = Suite(&isConnectedSuite{}) 45 46 func (s *isConnectedSuite) SetUpTest(c *C) { 47 s.BaseTest.SetUpTest(c) 48 dirs.SetRootDir(c.MkDir()) 49 s.AddCleanup(func() { dirs.SetRootDir("/") }) 50 s.st = state.New(nil) 51 s.mockHandler = hooktest.NewMockHandler() 52 } 53 54 var isConnectedTests = []struct { 55 args []string 56 stdout, stderr, err string 57 exitCode int 58 }{{ 59 args: []string{"is-connected"}, 60 err: "the required argument `<plug|slot>` was not provided", 61 }, { 62 args: []string{"is-connected", "plug1"}, 63 }, { 64 args: []string{"is-connected", "slot1"}, 65 }, { 66 // reported as not connected because of undesired flag 67 args: []string{"is-connected", "plug2"}, 68 exitCode: 1, 69 }, { 70 // reported as not connected because of hotplug-gone flag 71 args: []string{"is-connected", "plug3"}, 72 exitCode: 1, 73 }, { 74 args: []string{"is-connected", "slot2"}, 75 err: `snap "snap1" has no plug or slot named "slot2"`, 76 }, { 77 args: []string{"is-connected", "foo"}, 78 err: `snap "snap1" has no plug or slot named "foo"`, 79 }, { 80 // snap1:plug1 does not use an allowed interface 81 args: []string{"is-connected", "--pid", "1002", "plug1"}, 82 err: `cannot use --pid check with snap1:plug1`, 83 }, { 84 // snap1:slot1 does not use an allowed interface 85 args: []string{"is-connected", "--pid", "1002", "slot1"}, 86 err: `cannot use --pid check with snap1:slot1`, 87 }, { 88 // snap1:cc slot is not connected to snap2 89 args: []string{"is-connected", "--pid", "1002", "cc"}, 90 exitCode: 1, 91 }, { 92 // snap1:cc slot is connected to snap3 93 args: []string{"is-connected", "--pid", "1003", "cc"}, 94 exitCode: 0, 95 }, { 96 // snap1:cc slot is not connected to a non-snap pid 97 args: []string{"is-connected", "--pid", "42", "cc"}, 98 exitCode: ctlcmd.NotASnapCode, 99 }, { 100 // snap1:cc slot is connected to a classic snap5 101 args: []string{"is-connected", "--pid", "1005", "cc"}, 102 exitCode: 0, 103 }, { 104 // snap1:audio-record slot is not connected to classic snap5 105 args: []string{"is-connected", "--pid", "1005", "audio-record"}, 106 exitCode: ctlcmd.ClassicSnapCode, 107 }, { 108 // snap1:plug1 does not use an allowed interface 109 args: []string{"is-connected", "--apparmor-label", "snap.snap2.app", "plug1"}, 110 err: `cannot use --apparmor-label check with snap1:plug1`, 111 }, { 112 // snap1:slot1 does not use an allowed interface 113 args: []string{"is-connected", "--apparmor-label", "snap.snap2.app", "slot1"}, 114 err: `cannot use --apparmor-label check with snap1:slot1`, 115 }, { 116 // snap1:cc slot is not connected to snap2 117 args: []string{"is-connected", "--apparmor-label", "snap.snap2.app", "cc"}, 118 exitCode: 1, 119 }, { 120 // snap1:cc slot is connected to snap3 121 args: []string{"is-connected", "--apparmor-label", "snap.snap3.app", "cc"}, 122 exitCode: 0, 123 }, { 124 // snap1:cc slot is not connected to a non-snap pid 125 args: []string{"is-connected", "--apparmor-label", "/usr/bin/evince", "cc"}, 126 exitCode: ctlcmd.NotASnapCode, 127 }, { 128 // snap1:cc slot is connected to a classic snap5 129 args: []string{"is-connected", "--apparmor-label", "snap.snap5.app", "cc"}, 130 exitCode: 0, 131 }, { 132 // snap1:audio-record slot is not connected to classic snap5 133 args: []string{"is-connected", "--apparmor-label", "snap.snap5.app", "audio-record"}, 134 exitCode: ctlcmd.ClassicSnapCode, 135 }} 136 137 func mockInstalledSnap(c *C, st *state.State, snapYaml string) { 138 info := snaptest.MockSnapCurrent(c, snapYaml, &snap.SideInfo{Revision: snap.R(1)}) 139 snapstate.Set(st, info.InstanceName(), &snapstate.SnapState{ 140 Active: true, 141 Sequence: []*snap.SideInfo{ 142 { 143 RealName: info.SnapName(), 144 Revision: info.Revision, 145 SnapID: info.InstanceName() + "-id", 146 }, 147 }, 148 Current: info.Revision, 149 }) 150 } 151 152 func (s *isConnectedSuite) testIsConnected(c *C, context *hookstate.Context) { 153 mockInstalledSnap(c, s.st, `name: snap1 154 plugs: 155 plug1: 156 interface: x11 157 plug2: 158 interface: x11 159 plug3: 160 interface: x11 161 slots: 162 slot1: 163 interface: x11 164 cc: 165 interface: cups-control 166 audio-record: 167 interface: audio-record`) 168 mockInstalledSnap(c, s.st, `name: snap2 169 slots: 170 slot2: 171 interface: x11`) 172 mockInstalledSnap(c, s.st, `name: snap3 173 plugs: 174 plug4: 175 interface: x11 176 cc: 177 interface: cups-control 178 slots: 179 slot3: 180 interface: x11`) 181 mockInstalledSnap(c, s.st, `name: snap4 182 slots: 183 slot4: 184 interface: x11`) 185 mockInstalledSnap(c, s.st, `name: snap5 186 confinement: classic 187 plugs: 188 cc: 189 interface: cups-control`) 190 restore := ctlcmd.MockCgroupSnapNameFromPid(func(pid int) (string, error) { 191 switch { 192 case 1000 < pid && pid < 1100: 193 return fmt.Sprintf("snap%d", pid-1000), nil 194 default: 195 return "", fmt.Errorf("Not a snap") 196 } 197 }) 198 defer restore() 199 200 s.st.Set("conns", map[string]interface{}{ 201 "snap1:plug1 snap2:slot2": map[string]interface{}{}, 202 "snap1:plug2 snap3:slot3": map[string]interface{}{"undesired": true}, 203 "snap1:plug3 snap4:slot4": map[string]interface{}{"hotplug-gone": true}, 204 "snap3:plug4 snap1:slot1": map[string]interface{}{}, 205 "snap3:cc snap1:cc": map[string]interface{}{}, 206 "snap5:cc snap1:cc": map[string]interface{}{}, 207 }) 208 209 s.st.Unlock() 210 defer s.st.Lock() 211 212 for _, test := range isConnectedTests { 213 stdout, stderr, err := ctlcmd.Run(context, test.args, 0) 214 comment := Commentf("%s", test.args) 215 if test.exitCode > 0 { 216 c.Check(err, DeepEquals, &ctlcmd.UnsuccessfulError{ExitCode: test.exitCode}, comment) 217 } else { 218 if test.err == "" { 219 c.Check(err, IsNil, comment) 220 } else { 221 c.Check(err, ErrorMatches, test.err, comment) 222 } 223 } 224 225 c.Check(string(stdout), Equals, test.stdout, comment) 226 c.Check(string(stderr), Equals, "", comment) 227 } 228 } 229 230 func (s *isConnectedSuite) TestIsConnectedFromHook(c *C) { 231 s.st.Lock() 232 defer s.st.Unlock() 233 234 task := s.st.NewTask("test-task", "my test task") 235 setup := &hookstate.HookSetup{Snap: "snap1", Revision: snap.R(1), Hook: "test-hook"} 236 237 mockContext, err := hookstate.NewContext(task, s.st, setup, s.mockHandler, "") 238 c.Check(err, IsNil) 239 240 s.testIsConnected(c, mockContext) 241 } 242 243 func (s *isConnectedSuite) TestIsConnectedFromApp(c *C) { 244 s.st.Lock() 245 defer s.st.Unlock() 246 247 // ephemeral context 248 setup := &hookstate.HookSetup{Snap: "snap1", Revision: snap.R(1)} 249 mockContext, err := hookstate.NewContext(nil, s.st, setup, s.mockHandler, "") 250 c.Check(err, IsNil) 251 252 // sanity 253 c.Assert(mockContext.IsEphemeral(), Equals, true) 254 255 s.testIsConnected(c, mockContext) 256 } 257 258 func (s *isConnectedSuite) TestNoContextError(c *C) { 259 stdout, stderr, err := ctlcmd.Run(nil, []string{"is-connected", "foo"}, 0) 260 c.Check(err, ErrorMatches, `cannot check connection status without a context`) 261 c.Check(string(stdout), Equals, "") 262 c.Check(string(stderr), Equals, "") 263 } 264 265 func (s *isConnectedSuite) TestGetRegularUser(c *C) { 266 s.st.Lock() 267 268 mockInstalledSnap(c, s.st, `name: snap1 269 plugs: 270 plug1: 271 interface: x11`) 272 273 s.st.Set("conns", map[string]interface{}{ 274 "snap1:plug1 snap2:slot2": map[string]interface{}{}, 275 }) 276 277 setup := &hookstate.HookSetup{Snap: "snap1", Revision: snap.R(1)} 278 279 s.st.Unlock() 280 281 mockContext, err := hookstate.NewContext(nil, s.st, setup, s.mockHandler, "") 282 c.Assert(err, IsNil) 283 stdout, stderr, err := ctlcmd.Run(mockContext, []string{"is-connected", "plug1"}, 1000) 284 c.Check(err, IsNil) 285 c.Check(string(stdout), Equals, "") 286 c.Check(string(stderr), Equals, "") 287 }