gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/interfaces/repo_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 interfaces_test
    21  
    22  import (
    23  	"fmt"
    24  	"strings"
    25  
    26  	. "gopkg.in/check.v1"
    27  
    28  	. "github.com/snapcore/snapd/interfaces"
    29  	"github.com/snapcore/snapd/interfaces/ifacetest"
    30  	"github.com/snapcore/snapd/snap"
    31  	"github.com/snapcore/snapd/snap/snaptest"
    32  	"github.com/snapcore/snapd/testutil"
    33  )
    34  
    35  type RepositorySuite struct {
    36  	testutil.BaseTest
    37  	iface     Interface
    38  	plug      *snap.PlugInfo
    39  	plugSelf  *snap.PlugInfo
    40  	slot      *snap.SlotInfo
    41  	emptyRepo *Repository
    42  	// Repository pre-populated with s.iface
    43  	testRepo *Repository
    44  
    45  	// "Core"-like snaps with the same set of interfaces.
    46  	coreSnap       *snap.Info
    47  	ubuntuCoreSnap *snap.Info
    48  	snapdSnap      *snap.Info
    49  }
    50  
    51  var _ = Suite(&RepositorySuite{
    52  	iface: &ifacetest.TestInterface{
    53  		InterfaceName: "interface",
    54  	},
    55  })
    56  
    57  const consumerYaml = `
    58  name: consumer
    59  version: 0
    60  apps:
    61      app:
    62  hooks:
    63      configure:
    64  plugs:
    65      plug:
    66          interface: interface
    67          label: label
    68          attr: value
    69  `
    70  
    71  const producerYaml = `
    72  name: producer
    73  version: 0
    74  apps:
    75      app:
    76  hooks:
    77      configure:
    78  slots:
    79      slot:
    80          interface: interface
    81          label: label
    82          attr: value
    83  plugs:
    84      self:
    85          interface: interface
    86          label: label
    87  `
    88  
    89  func (s *RepositorySuite) SetUpTest(c *C) {
    90  	s.BaseTest.SetUpTest(c)
    91  	s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
    92  
    93  	consumer := snaptest.MockInfo(c, consumerYaml, nil)
    94  	s.plug = consumer.Plugs["plug"]
    95  	producer := snaptest.MockInfo(c, producerYaml, nil)
    96  	s.slot = producer.Slots["slot"]
    97  	s.plugSelf = producer.Plugs["self"]
    98  	// NOTE: Each of the snaps below have one slot so that they can be picked
    99  	// up by the repository. Some tests rename the "slot" slot as appropriate.
   100  	s.ubuntuCoreSnap = snaptest.MockInfo(c, `
   101  name: ubuntu-core
   102  version: 0
   103  type: os
   104  slots:
   105      slot:
   106          interface: interface
   107  `, nil)
   108  	// NOTE: The core snap has a slot so that it shows up in the
   109  	// repository. The repository doesn't record snaps unless they
   110  	// have at least one interface.
   111  	s.coreSnap = snaptest.MockInfo(c, `
   112  name: core
   113  version: 0
   114  type: os
   115  slots:
   116      slot:
   117          interface: interface
   118  `, nil)
   119  	s.snapdSnap = snaptest.MockInfo(c, `
   120  name: snapd
   121  version: 0
   122  type: app
   123  slots:
   124      slot:
   125          interface: interface
   126  `, nil)
   127  
   128  	s.emptyRepo = NewRepository()
   129  	s.testRepo = NewRepository()
   130  	err := s.testRepo.AddInterface(s.iface)
   131  	c.Assert(err, IsNil)
   132  }
   133  
   134  func (s *RepositorySuite) TearDownTest(c *C) {
   135  	s.BaseTest.TearDownTest(c)
   136  }
   137  
   138  type instanceNameAndYaml struct {
   139  	Name string
   140  	Yaml string
   141  }
   142  
   143  func addPlugsSlotsFromInstances(c *C, repo *Repository, iys []instanceNameAndYaml) []*snap.Info {
   144  	result := make([]*snap.Info, 0, len(iys))
   145  	for _, iy := range iys {
   146  		info := snaptest.MockInfo(c, iy.Yaml, nil)
   147  		if iy.Name != "" {
   148  			instanceName := iy.Name
   149  			c.Assert(snap.ValidateInstanceName(instanceName), IsNil)
   150  			_, info.InstanceKey = snap.SplitInstanceName(instanceName)
   151  		}
   152  
   153  		result = append(result, info)
   154  		for _, plugInfo := range info.Plugs {
   155  			err := repo.AddPlug(plugInfo)
   156  			c.Assert(err, IsNil)
   157  		}
   158  		for _, slotInfo := range info.Slots {
   159  			err := repo.AddSlot(slotInfo)
   160  			c.Assert(err, IsNil)
   161  		}
   162  	}
   163  	return result
   164  }
   165  
   166  // Tests for Repository.AddInterface()
   167  
   168  func (s *RepositorySuite) TestAddInterface(c *C) {
   169  	// Adding a valid interfaces works
   170  	err := s.emptyRepo.AddInterface(s.iface)
   171  	c.Assert(err, IsNil)
   172  	c.Assert(s.emptyRepo.Interface(s.iface.Name()), Equals, s.iface)
   173  }
   174  
   175  func (s *RepositorySuite) TestAddInterfaceClash(c *C) {
   176  	iface1 := &ifacetest.TestInterface{InterfaceName: "iface"}
   177  	iface2 := &ifacetest.TestInterface{InterfaceName: "iface"}
   178  	err := s.emptyRepo.AddInterface(iface1)
   179  	c.Assert(err, IsNil)
   180  	// Adding an interface with the same name as another interface is not allowed
   181  	err = s.emptyRepo.AddInterface(iface2)
   182  	c.Assert(err, ErrorMatches, `cannot add interface: "iface", interface name is in use`)
   183  	c.Assert(s.emptyRepo.Interface(iface1.Name()), Equals, iface1)
   184  }
   185  
   186  func (s *RepositorySuite) TestAddInterfaceInvalidName(c *C) {
   187  	iface := &ifacetest.TestInterface{InterfaceName: "bad-name-"}
   188  	// Adding an interface with invalid name is not allowed
   189  	err := s.emptyRepo.AddInterface(iface)
   190  	c.Assert(err, ErrorMatches, `invalid interface name: "bad-name-"`)
   191  	c.Assert(s.emptyRepo.Interface(iface.Name()), IsNil)
   192  }
   193  
   194  // Tests for Repository.AllInterfaces()
   195  
   196  func (s *RepositorySuite) TestAllInterfaces(c *C) {
   197  	c.Assert(s.emptyRepo.AllInterfaces(), HasLen, 0)
   198  	c.Assert(s.testRepo.AllInterfaces(), DeepEquals, []Interface{s.iface})
   199  
   200  	// Add three interfaces in some non-sorted order.
   201  	i1 := &ifacetest.TestInterface{InterfaceName: "i1"}
   202  	i2 := &ifacetest.TestInterface{InterfaceName: "i2"}
   203  	i3 := &ifacetest.TestInterface{InterfaceName: "i3"}
   204  	c.Assert(s.emptyRepo.AddInterface(i3), IsNil)
   205  	c.Assert(s.emptyRepo.AddInterface(i1), IsNil)
   206  	c.Assert(s.emptyRepo.AddInterface(i2), IsNil)
   207  
   208  	// The result is always sorted.
   209  	c.Assert(s.emptyRepo.AllInterfaces(), DeepEquals, []Interface{i1, i2, i3})
   210  
   211  }
   212  
   213  func (s *RepositorySuite) TestAddBackend(c *C) {
   214  	backend := &ifacetest.TestSecurityBackend{BackendName: "test"}
   215  	c.Assert(s.emptyRepo.AddBackend(backend), IsNil)
   216  	err := s.emptyRepo.AddBackend(backend)
   217  	c.Assert(err, ErrorMatches, `cannot add backend "test", security system name is in use`)
   218  }
   219  
   220  func (s *RepositorySuite) TestBackends(c *C) {
   221  	b1 := &ifacetest.TestSecurityBackend{BackendName: "b1"}
   222  	b2 := &ifacetest.TestSecurityBackend{BackendName: "b2"}
   223  	c.Assert(s.emptyRepo.AddBackend(b2), IsNil)
   224  	c.Assert(s.emptyRepo.AddBackend(b1), IsNil)
   225  	// The order of insertion is retained.
   226  	c.Assert(s.emptyRepo.Backends(), DeepEquals, []SecurityBackend{b2, b1})
   227  }
   228  
   229  // Tests for Repository.Interface()
   230  
   231  func (s *RepositorySuite) TestInterface(c *C) {
   232  	// Interface returns nil when it cannot be found
   233  	iface := s.emptyRepo.Interface(s.iface.Name())
   234  	c.Assert(iface, IsNil)
   235  	c.Assert(s.emptyRepo.Interface(s.iface.Name()), IsNil)
   236  	err := s.emptyRepo.AddInterface(s.iface)
   237  	c.Assert(err, IsNil)
   238  	// Interface returns the found interface
   239  	iface = s.emptyRepo.Interface(s.iface.Name())
   240  	c.Assert(iface, Equals, s.iface)
   241  }
   242  
   243  func (s *RepositorySuite) TestInterfaceSearch(c *C) {
   244  	ifaceA := &ifacetest.TestInterface{InterfaceName: "a"}
   245  	ifaceB := &ifacetest.TestInterface{InterfaceName: "b"}
   246  	ifaceC := &ifacetest.TestInterface{InterfaceName: "c"}
   247  	err := s.emptyRepo.AddInterface(ifaceA)
   248  	c.Assert(err, IsNil)
   249  	err = s.emptyRepo.AddInterface(ifaceB)
   250  	c.Assert(err, IsNil)
   251  	err = s.emptyRepo.AddInterface(ifaceC)
   252  	c.Assert(err, IsNil)
   253  	// Interface correctly finds interfaces
   254  	c.Assert(s.emptyRepo.Interface("a"), Equals, ifaceA)
   255  	c.Assert(s.emptyRepo.Interface("b"), Equals, ifaceB)
   256  	c.Assert(s.emptyRepo.Interface("c"), Equals, ifaceC)
   257  }
   258  
   259  // Tests for Repository.AddPlug()
   260  
   261  func (s *RepositorySuite) TestAddPlug(c *C) {
   262  	c.Assert(s.testRepo.AllPlugs(""), HasLen, 0)
   263  	err := s.testRepo.AddPlug(s.plug)
   264  	c.Assert(err, IsNil)
   265  	c.Assert(s.testRepo.AllPlugs(""), HasLen, 1)
   266  	c.Assert(s.testRepo.Plug(s.plug.Snap.InstanceName(), s.plug.Name), DeepEquals, s.plug)
   267  }
   268  
   269  func (s *RepositorySuite) TestAddPlugClashingPlug(c *C) {
   270  	err := s.testRepo.AddPlug(s.plug)
   271  	c.Assert(err, IsNil)
   272  	err = s.testRepo.AddPlug(s.plug)
   273  	c.Assert(err, ErrorMatches, `snap "consumer" has plugs conflicting on name "plug"`)
   274  	c.Assert(s.testRepo.AllPlugs(""), HasLen, 1)
   275  	c.Assert(s.testRepo.Plug(s.plug.Snap.InstanceName(), s.plug.Name), DeepEquals, s.plug)
   276  }
   277  
   278  func (s *RepositorySuite) TestAddPlugClashingSlot(c *C) {
   279  	snapInfo := &snap.Info{SuggestedName: "snap"}
   280  	plug := &snap.PlugInfo{
   281  		Snap:      snapInfo,
   282  		Name:      "clashing",
   283  		Interface: "interface",
   284  	}
   285  	slot := &snap.SlotInfo{
   286  		Snap:      snapInfo,
   287  		Name:      "clashing",
   288  		Interface: "interface",
   289  	}
   290  	err := s.testRepo.AddSlot(slot)
   291  	c.Assert(err, IsNil)
   292  	err = s.testRepo.AddPlug(plug)
   293  	c.Assert(err, ErrorMatches, `snap "snap" has plug and slot conflicting on name "clashing"`)
   294  	c.Assert(s.testRepo.AllSlots(""), HasLen, 1)
   295  	c.Assert(s.testRepo.Slot(slot.Snap.InstanceName(), slot.Name), DeepEquals, slot)
   296  }
   297  
   298  func (s *RepositorySuite) TestAddPlugFailsWithInvalidSnapName(c *C) {
   299  	plug := &snap.PlugInfo{
   300  		Snap:      &snap.Info{SuggestedName: "bad-snap-"},
   301  		Name:      "interface",
   302  		Interface: "interface",
   303  	}
   304  	err := s.testRepo.AddPlug(plug)
   305  	c.Assert(err, ErrorMatches, `invalid snap name: "bad-snap-"`)
   306  	c.Assert(s.testRepo.AllPlugs(""), HasLen, 0)
   307  }
   308  
   309  func (s *RepositorySuite) TestAddPlugFailsWithInvalidPlugName(c *C) {
   310  	plug := &snap.PlugInfo{
   311  		Snap:      &snap.Info{SuggestedName: "snap"},
   312  		Name:      "bad-name-",
   313  		Interface: "interface",
   314  	}
   315  	err := s.testRepo.AddPlug(plug)
   316  	c.Assert(err, ErrorMatches, `invalid plug name: "bad-name-"`)
   317  	c.Assert(s.testRepo.AllPlugs(""), HasLen, 0)
   318  }
   319  
   320  func (s *RepositorySuite) TestAddPlugFailsWithUnknownInterface(c *C) {
   321  	err := s.emptyRepo.AddPlug(s.plug)
   322  	c.Assert(err, ErrorMatches, `cannot add plug, interface "interface" is not known`)
   323  	c.Assert(s.emptyRepo.AllPlugs(""), HasLen, 0)
   324  }
   325  
   326  func (s *RepositorySuite) TestAddPlugParallelInstance(c *C) {
   327  	c.Assert(s.testRepo.AllPlugs(""), HasLen, 0)
   328  
   329  	err := s.testRepo.AddPlug(s.plug)
   330  	c.Assert(err, IsNil)
   331  	c.Assert(s.testRepo.AllPlugs(""), HasLen, 1)
   332  
   333  	consumer := snaptest.MockInfo(c, consumerYaml, nil)
   334  	consumer.InstanceKey = "instance"
   335  	err = s.testRepo.AddPlug(consumer.Plugs["plug"])
   336  	c.Assert(err, IsNil)
   337  	c.Assert(s.testRepo.AllPlugs(""), HasLen, 2)
   338  
   339  	c.Assert(s.testRepo.Plug(s.plug.Snap.InstanceName(), s.plug.Name), DeepEquals, s.plug)
   340  	c.Assert(s.testRepo.Plug(consumer.InstanceName(), "plug"), DeepEquals, consumer.Plugs["plug"])
   341  }
   342  
   343  // Tests for Repository.Plug()
   344  
   345  func (s *RepositorySuite) TestPlug(c *C) {
   346  	err := s.testRepo.AddPlug(s.plug)
   347  	c.Assert(err, IsNil)
   348  	c.Assert(s.emptyRepo.Plug(s.plug.Snap.InstanceName(), s.plug.Name), IsNil)
   349  	c.Assert(s.testRepo.Plug(s.plug.Snap.InstanceName(), s.plug.Name), DeepEquals, s.plug)
   350  }
   351  
   352  func (s *RepositorySuite) TestPlugSearch(c *C) {
   353  	addPlugsSlotsFromInstances(c, s.testRepo, []instanceNameAndYaml{
   354  		{Name: "xx", Yaml: `
   355  name: xx
   356  version: 0
   357  plugs:
   358      a: interface
   359      b: interface
   360      c: interface
   361  `},
   362  		{Name: "yy", Yaml: `
   363  name: yy
   364  version: 0
   365  plugs:
   366      a: interface
   367      b: interface
   368      c: interface
   369  `},
   370  		{Name: "zz_instance", Yaml: `
   371  name: zz
   372  version: 0
   373  plugs:
   374      a: interface
   375      b: interface
   376      c: interface
   377  `},
   378  	})
   379  	// Plug() correctly finds plugs
   380  	c.Assert(s.testRepo.Plug("xx", "a"), Not(IsNil))
   381  	c.Assert(s.testRepo.Plug("xx", "b"), Not(IsNil))
   382  	c.Assert(s.testRepo.Plug("xx", "c"), Not(IsNil))
   383  	c.Assert(s.testRepo.Plug("yy", "a"), Not(IsNil))
   384  	c.Assert(s.testRepo.Plug("yy", "b"), Not(IsNil))
   385  	c.Assert(s.testRepo.Plug("yy", "c"), Not(IsNil))
   386  	c.Assert(s.testRepo.Plug("zz_instance", "a"), Not(IsNil))
   387  	c.Assert(s.testRepo.Plug("zz_instance", "b"), Not(IsNil))
   388  	c.Assert(s.testRepo.Plug("zz_instance", "c"), Not(IsNil))
   389  }
   390  
   391  // Tests for Repository.RemovePlug()
   392  
   393  func (s *RepositorySuite) TestRemovePlugSucceedsWhenPlugExistsAndDisconnected(c *C) {
   394  	err := s.testRepo.AddPlug(s.plug)
   395  	c.Assert(err, IsNil)
   396  	err = s.testRepo.RemovePlug(s.plug.Snap.InstanceName(), s.plug.Name)
   397  	c.Assert(err, IsNil)
   398  	c.Assert(s.testRepo.AllPlugs(""), HasLen, 0)
   399  }
   400  
   401  func (s *RepositorySuite) TestRemovePlugFailsWhenPlugDoesntExist(c *C) {
   402  	err := s.emptyRepo.RemovePlug(s.plug.Snap.InstanceName(), s.plug.Name)
   403  	c.Assert(err, ErrorMatches, `cannot remove plug "plug" from snap "consumer", no such plug`)
   404  }
   405  
   406  func (s *RepositorySuite) TestRemovePlugFailsWhenPlugIsConnected(c *C) {
   407  	err := s.testRepo.AddPlug(s.plug)
   408  	c.Assert(err, IsNil)
   409  	err = s.testRepo.AddSlot(s.slot)
   410  	c.Assert(err, IsNil)
   411  	connRef := NewConnRef(s.plug, s.slot)
   412  	_, err = s.testRepo.Connect(connRef, nil, nil, nil, nil, nil)
   413  	c.Assert(err, IsNil)
   414  	// Removing a plug used by a slot returns an appropriate error
   415  	err = s.testRepo.RemovePlug(s.plug.Snap.InstanceName(), s.plug.Name)
   416  	c.Assert(err, ErrorMatches, `cannot remove plug "plug" from snap "consumer", it is still connected`)
   417  	// The plug is still there
   418  	slot := s.testRepo.Plug(s.plug.Snap.InstanceName(), s.plug.Name)
   419  	c.Assert(slot, Not(IsNil))
   420  }
   421  
   422  // Tests for Repository.AllPlugs()
   423  
   424  func (s *RepositorySuite) TestAllPlugsWithoutInterfaceName(c *C) {
   425  	snaps := addPlugsSlotsFromInstances(c, s.testRepo, []instanceNameAndYaml{
   426  		{Name: "snap-a", Yaml: `
   427  name: snap-a
   428  version: 0
   429  plugs:
   430      name-a: interface
   431  `},
   432  		{Name: "snap-b", Yaml: `
   433  name: snap-b
   434  version: 0
   435  plugs:
   436      name-a: interface
   437      name-b: interface
   438      name-c: interface
   439  `},
   440  		{Name: "snap-b_instance", Yaml: `
   441  name: snap-b
   442  version: 0
   443  plugs:
   444      name-a: interface
   445      name-b: interface
   446      name-c: interface
   447  `},
   448  	})
   449  	c.Assert(snaps, HasLen, 3)
   450  	// The result is sorted by snap and name
   451  	c.Assert(s.testRepo.AllPlugs(""), DeepEquals, []*snap.PlugInfo{
   452  		snaps[0].Plugs["name-a"],
   453  		snaps[1].Plugs["name-a"],
   454  		snaps[1].Plugs["name-b"],
   455  		snaps[1].Plugs["name-c"],
   456  		snaps[2].Plugs["name-a"],
   457  		snaps[2].Plugs["name-b"],
   458  		snaps[2].Plugs["name-c"],
   459  	})
   460  }
   461  
   462  func (s *RepositorySuite) TestAllPlugsWithInterfaceName(c *C) {
   463  	// Add another interface so that we can look for it
   464  	err := s.testRepo.AddInterface(&ifacetest.TestInterface{InterfaceName: "other-interface"})
   465  	c.Assert(err, IsNil)
   466  	snaps := addPlugsSlotsFromInstances(c, s.testRepo, []instanceNameAndYaml{
   467  		{Name: "snap-a", Yaml: `
   468  name: snap-a
   469  version: 0
   470  plugs:
   471      name-a: interface
   472  `},
   473  		{Name: "snap-b", Yaml: `
   474  name: snap-b
   475  version: 0
   476  plugs:
   477      name-a: interface
   478      name-b: other-interface
   479      name-c: interface
   480  `},
   481  		{Name: "snap-b_instance", Yaml: `
   482  name: snap-b
   483  version: 0
   484  plugs:
   485      name-a: interface
   486      name-b: other-interface
   487      name-c: interface
   488  `},
   489  	})
   490  	c.Assert(snaps, HasLen, 3)
   491  	c.Assert(s.testRepo.AllPlugs("other-interface"), DeepEquals, []*snap.PlugInfo{
   492  		snaps[1].Plugs["name-b"],
   493  		snaps[2].Plugs["name-b"],
   494  	})
   495  }
   496  
   497  // Tests for Repository.Plugs()
   498  
   499  func (s *RepositorySuite) TestPlugs(c *C) {
   500  	snaps := addPlugsSlotsFromInstances(c, s.testRepo, []instanceNameAndYaml{
   501  		{Name: "snap-a", Yaml: `
   502  name: snap-a
   503  version: 0
   504  plugs:
   505      name-a: interface
   506  `},
   507  		{Name: "snap-b", Yaml: `
   508  name: snap-b
   509  version: 0
   510  plugs:
   511      name-a: interface
   512      name-b: interface
   513      name-c: interface
   514  `},
   515  		{Name: "snap-b_instance", Yaml: `
   516  name: snap-b
   517  version: 0
   518  plugs:
   519      name-a: interface
   520      name-b: interface
   521      name-c: interface
   522  `},
   523  	})
   524  	c.Assert(snaps, HasLen, 3)
   525  	// The result is sorted by snap and name
   526  	c.Assert(s.testRepo.Plugs("snap-b"), DeepEquals, []*snap.PlugInfo{
   527  		snaps[1].Plugs["name-a"],
   528  		snaps[1].Plugs["name-b"],
   529  		snaps[1].Plugs["name-c"],
   530  	})
   531  	c.Assert(s.testRepo.Plugs("snap-b_instance"), DeepEquals, []*snap.PlugInfo{
   532  		snaps[2].Plugs["name-a"],
   533  		snaps[2].Plugs["name-b"],
   534  		snaps[2].Plugs["name-c"],
   535  	})
   536  	// The result is empty if the snap is not known
   537  	c.Assert(s.testRepo.Plugs("snap-x"), HasLen, 0)
   538  	c.Assert(s.testRepo.Plugs("snap-b_other"), HasLen, 0)
   539  }
   540  
   541  // Tests for Repository.AllSlots()
   542  
   543  func (s *RepositorySuite) TestAllSlots(c *C) {
   544  	err := s.testRepo.AddInterface(&ifacetest.TestInterface{InterfaceName: "other-interface"})
   545  	c.Assert(err, IsNil)
   546  	snaps := addPlugsSlotsFromInstances(c, s.testRepo, []instanceNameAndYaml{
   547  		{Name: "snap-a", Yaml: `
   548  name: snap-a
   549  version: 0
   550  slots:
   551      name-a: interface
   552      name-b: interface
   553  `},
   554  		{Name: "snap-b", Yaml: `
   555  name: snap-b
   556  version: 0
   557  slots:
   558      name-a: other-interface
   559  `},
   560  		{Name: "snap-b_instance", Yaml: `
   561  name: snap-b
   562  version: 0
   563  slots:
   564      name-a: other-interface
   565  `},
   566  	})
   567  	c.Assert(snaps, HasLen, 3)
   568  	// AllSlots("") returns all slots, sorted by snap and slot name
   569  	c.Assert(s.testRepo.AllSlots(""), DeepEquals, []*snap.SlotInfo{
   570  		snaps[0].Slots["name-a"],
   571  		snaps[0].Slots["name-b"],
   572  		snaps[1].Slots["name-a"],
   573  		snaps[2].Slots["name-a"],
   574  	})
   575  	// AllSlots("") returns all slots, sorted by snap and slot name
   576  	c.Assert(s.testRepo.AllSlots("other-interface"), DeepEquals, []*snap.SlotInfo{
   577  		snaps[1].Slots["name-a"],
   578  		snaps[2].Slots["name-a"],
   579  	})
   580  }
   581  
   582  // Tests for Repository.Slots()
   583  
   584  func (s *RepositorySuite) TestSlots(c *C) {
   585  	snaps := addPlugsSlotsFromInstances(c, s.testRepo, []instanceNameAndYaml{
   586  		{Name: "snap-a", Yaml: `
   587  name: snap-a
   588  version: 0
   589  slots:
   590      name-a: interface
   591      name-b: interface
   592  `},
   593  		{Name: "snap-b", Yaml: `
   594  name: snap-b
   595  version: 0
   596  slots:
   597      name-a: interface
   598  `},
   599  		{Name: "snap-b_instance", Yaml: `
   600  name: snap-b
   601  version: 0
   602  slots:
   603      name-a: interface
   604  `},
   605  	})
   606  	// Slots("snap-a") returns slots present in that snap
   607  	c.Assert(s.testRepo.Slots("snap-a"), DeepEquals, []*snap.SlotInfo{
   608  		snaps[0].Slots["name-a"],
   609  		snaps[0].Slots["name-b"],
   610  	})
   611  	// Slots("snap-b") returns slots present in that snap
   612  	c.Assert(s.testRepo.Slots("snap-b"), DeepEquals, []*snap.SlotInfo{
   613  		snaps[1].Slots["name-a"],
   614  	})
   615  	// Slots("snap-b_instance") returns slots present in that snap
   616  	c.Assert(s.testRepo.Slots("snap-b_instance"), DeepEquals, []*snap.SlotInfo{
   617  		snaps[2].Slots["name-a"],
   618  	})
   619  	// Slots("snap-c") returns no slots (because that snap doesn't exist)
   620  	c.Assert(s.testRepo.Slots("snap-c"), HasLen, 0)
   621  	// Slots("snap-b_other") returns no slots (the snap does not exist)
   622  	c.Assert(s.testRepo.Slots("snap-b_other"), HasLen, 0)
   623  	// Slots("") returns no slots
   624  	c.Assert(s.testRepo.Slots(""), HasLen, 0)
   625  }
   626  
   627  // Tests for Repository.Slot()
   628  
   629  func (s *RepositorySuite) TestSlotSucceedsWhenSlotExists(c *C) {
   630  	err := s.testRepo.AddSlot(s.slot)
   631  	c.Assert(err, IsNil)
   632  	slot := s.testRepo.Slot(s.slot.Snap.InstanceName(), s.slot.Name)
   633  	c.Assert(slot, DeepEquals, s.slot)
   634  }
   635  
   636  func (s *RepositorySuite) TestSlotFailsWhenSlotDoesntExist(c *C) {
   637  	slot := s.testRepo.Slot(s.slot.Snap.InstanceName(), s.slot.Name)
   638  	c.Assert(slot, IsNil)
   639  }
   640  
   641  // Tests for Repository.AddSlot()
   642  
   643  func (s *RepositorySuite) TestAddSlotFailsWhenInterfaceIsUnknown(c *C) {
   644  	err := s.emptyRepo.AddSlot(s.slot)
   645  	c.Assert(err, ErrorMatches, `cannot add slot, interface "interface" is not known`)
   646  }
   647  
   648  func (s *RepositorySuite) TestAddSlotFailsWhenSlotNameIsInvalid(c *C) {
   649  	slot := &snap.SlotInfo{
   650  		Snap:      &snap.Info{SuggestedName: "snap"},
   651  		Name:      "bad-name-",
   652  		Interface: "interface",
   653  	}
   654  	err := s.emptyRepo.AddSlot(slot)
   655  	c.Assert(err, ErrorMatches, `invalid slot name: "bad-name-"`)
   656  	c.Assert(s.emptyRepo.AllSlots(""), HasLen, 0)
   657  }
   658  
   659  func (s *RepositorySuite) TestAddSlotFailsWithInvalidSnapName(c *C) {
   660  	slot := &snap.SlotInfo{
   661  		Snap:      &snap.Info{SuggestedName: "bad-snap-"},
   662  		Name:      "slot",
   663  		Interface: "interface",
   664  	}
   665  	err := s.emptyRepo.AddSlot(slot)
   666  	c.Assert(err, ErrorMatches, `invalid snap name: "bad-snap-"`)
   667  	c.Assert(s.emptyRepo.AllSlots(""), HasLen, 0)
   668  }
   669  
   670  func (s *RepositorySuite) TestAddSlotClashingSlot(c *C) {
   671  	// Adding the first slot succeeds
   672  	err := s.testRepo.AddSlot(s.slot)
   673  	c.Assert(err, IsNil)
   674  	// Adding the slot again fails with appropriate error
   675  	err = s.testRepo.AddSlot(s.slot)
   676  	c.Assert(err, ErrorMatches, `snap "producer" has slots conflicting on name "slot"`)
   677  }
   678  
   679  func (s *RepositorySuite) TestAddSlotClashingPlug(c *C) {
   680  	snapInfo := &snap.Info{SuggestedName: "snap"}
   681  	plug := &snap.PlugInfo{
   682  		Snap:      snapInfo,
   683  		Name:      "clashing",
   684  		Interface: "interface",
   685  	}
   686  	slot := &snap.SlotInfo{
   687  		Snap:      snapInfo,
   688  		Name:      "clashing",
   689  		Interface: "interface",
   690  	}
   691  	err := s.testRepo.AddPlug(plug)
   692  	c.Assert(err, IsNil)
   693  	err = s.testRepo.AddSlot(slot)
   694  	c.Assert(err, ErrorMatches, `snap "snap" has plug and slot conflicting on name "clashing"`)
   695  	c.Assert(s.testRepo.AllPlugs(""), HasLen, 1)
   696  	c.Assert(s.testRepo.Plug(plug.Snap.InstanceName(), plug.Name), DeepEquals, plug)
   697  }
   698  
   699  func (s *RepositorySuite) TestAddSlotStoresCorrectData(c *C) {
   700  	err := s.testRepo.AddSlot(s.slot)
   701  	c.Assert(err, IsNil)
   702  	slot := s.testRepo.Slot(s.slot.Snap.InstanceName(), s.slot.Name)
   703  	// The added slot has the same data
   704  	c.Assert(slot, DeepEquals, s.slot)
   705  }
   706  
   707  func (s *RepositorySuite) TestAddSlotParallelInstance(c *C) {
   708  	c.Assert(s.testRepo.AllSlots(""), HasLen, 0)
   709  
   710  	err := s.testRepo.AddSlot(s.slot)
   711  	c.Assert(err, IsNil)
   712  	c.Assert(s.testRepo.AllSlots(""), HasLen, 1)
   713  
   714  	producer := snaptest.MockInfo(c, producerYaml, nil)
   715  	producer.InstanceKey = "instance"
   716  	err = s.testRepo.AddSlot(producer.Slots["slot"])
   717  	c.Assert(err, IsNil)
   718  	c.Assert(s.testRepo.AllSlots(""), HasLen, 2)
   719  
   720  	c.Assert(s.testRepo.Slot(s.slot.Snap.InstanceName(), s.slot.Name), DeepEquals, s.slot)
   721  	c.Assert(s.testRepo.Slot(producer.InstanceName(), "slot"), DeepEquals, producer.Slots["slot"])
   722  }
   723  
   724  // Tests for Repository.RemoveSlot()
   725  
   726  func (s *RepositorySuite) TestRemoveSlotSuccedsWhenSlotExistsAndDisconnected(c *C) {
   727  	err := s.testRepo.AddSlot(s.slot)
   728  	c.Assert(err, IsNil)
   729  	// Removing a vacant slot simply works
   730  	err = s.testRepo.RemoveSlot(s.slot.Snap.InstanceName(), s.slot.Name)
   731  	c.Assert(err, IsNil)
   732  	// The slot is gone now
   733  	slot := s.testRepo.Slot(s.slot.Snap.InstanceName(), s.slot.Name)
   734  	c.Assert(slot, IsNil)
   735  }
   736  
   737  func (s *RepositorySuite) TestRemoveSlotFailsWhenSlotDoesntExist(c *C) {
   738  	// Removing a slot that doesn't exist returns an appropriate error
   739  	err := s.testRepo.RemoveSlot(s.slot.Snap.InstanceName(), s.slot.Name)
   740  	c.Assert(err, Not(IsNil))
   741  	c.Assert(err, ErrorMatches, `cannot remove slot "slot" from snap "producer", no such slot`)
   742  }
   743  
   744  func (s *RepositorySuite) TestRemoveSlotFailsWhenSlotIsConnected(c *C) {
   745  	err := s.testRepo.AddPlug(s.plug)
   746  	c.Assert(err, IsNil)
   747  	err = s.testRepo.AddSlot(s.slot)
   748  	c.Assert(err, IsNil)
   749  	connRef := NewConnRef(s.plug, s.slot)
   750  	_, err = s.testRepo.Connect(connRef, nil, nil, nil, nil, nil)
   751  	c.Assert(err, IsNil)
   752  	// Removing a slot occupied by a plug returns an appropriate error
   753  	err = s.testRepo.RemoveSlot(s.slot.Snap.InstanceName(), s.slot.Name)
   754  	c.Assert(err, ErrorMatches, `cannot remove slot "slot" from snap "producer", it is still connected`)
   755  	// The slot is still there
   756  	slot := s.testRepo.Slot(s.slot.Snap.InstanceName(), s.slot.Name)
   757  	c.Assert(slot, Not(IsNil))
   758  }
   759  
   760  // Tests for Repository.ResolveConnect()
   761  
   762  func (s *RepositorySuite) TestResolveConnectExplicit(c *C) {
   763  	c.Assert(s.testRepo.AddSlot(s.slot), IsNil)
   764  	c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
   765  	conn, err := s.testRepo.ResolveConnect("consumer", "plug", "producer", "slot")
   766  	c.Check(err, IsNil)
   767  	c.Check(conn, DeepEquals, &ConnRef{
   768  		PlugRef: PlugRef{Snap: "consumer", Name: "plug"},
   769  		SlotRef: SlotRef{Snap: "producer", Name: "slot"},
   770  	})
   771  }
   772  
   773  // ResolveConnect uses the "snapd" snap when slot snap name is empty
   774  func (s *RepositorySuite) TestResolveConnectImplicitSnapdSlot(c *C) {
   775  	c.Assert(s.testRepo.AddSnap(s.snapdSnap), IsNil)
   776  	c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
   777  	conn, err := s.testRepo.ResolveConnect("consumer", "plug", "", "slot")
   778  	c.Check(err, IsNil)
   779  	c.Check(conn, DeepEquals, &ConnRef{
   780  		PlugRef: PlugRef{Snap: "consumer", Name: "plug"},
   781  		SlotRef: SlotRef{Snap: "snapd", Name: "slot"},
   782  	})
   783  }
   784  
   785  // ResolveConnect uses the "core" snap when slot snap name is empty
   786  func (s *RepositorySuite) TestResolveConnectImplicitCoreSlot(c *C) {
   787  	c.Assert(s.testRepo.AddSnap(s.coreSnap), IsNil)
   788  	c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
   789  	conn, err := s.testRepo.ResolveConnect("consumer", "plug", "", "slot")
   790  	c.Check(err, IsNil)
   791  	c.Check(conn, DeepEquals, &ConnRef{
   792  		PlugRef: PlugRef{Snap: "consumer", Name: "plug"},
   793  		SlotRef: SlotRef{Snap: "core", Name: "slot"},
   794  	})
   795  }
   796  
   797  // ResolveConnect uses the "ubuntu-core" snap when slot snap name is empty
   798  func (s *RepositorySuite) TestResolveConnectImplicitUbuntuCoreSlot(c *C) {
   799  	c.Assert(s.testRepo.AddSnap(s.ubuntuCoreSnap), IsNil)
   800  	c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
   801  	conn, err := s.testRepo.ResolveConnect("consumer", "plug", "", "slot")
   802  	c.Check(err, IsNil)
   803  	c.Check(conn, DeepEquals, &ConnRef{
   804  		PlugRef: PlugRef{Snap: "consumer", Name: "plug"},
   805  		SlotRef: SlotRef{Snap: "ubuntu-core", Name: "slot"},
   806  	})
   807  }
   808  
   809  // ResolveConnect prefers the "snapd" snap if "snapd" and "core" are available
   810  func (s *RepositorySuite) TestResolveConnectImplicitSlotPrefersSnapdOverCore(c *C) {
   811  	c.Assert(s.testRepo.AddSnap(s.snapdSnap), IsNil)
   812  	c.Assert(s.testRepo.AddSnap(s.coreSnap), IsNil)
   813  	c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
   814  	conn, err := s.testRepo.ResolveConnect("consumer", "plug", "", "slot")
   815  	c.Check(err, IsNil)
   816  	c.Check(conn.SlotRef.Snap, Equals, "snapd")
   817  }
   818  
   819  // ResolveConnect prefers the "core" snap if "core" and "ubuntu-core" are available
   820  func (s *RepositorySuite) TestResolveConnectImplicitSlotPrefersCoreOverUbuntuCore(c *C) {
   821  	c.Assert(s.testRepo.AddSnap(s.coreSnap), IsNil)
   822  	c.Assert(s.testRepo.AddSnap(s.ubuntuCoreSnap), IsNil)
   823  	c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
   824  	conn, err := s.testRepo.ResolveConnect("consumer", "plug", "", "slot")
   825  	c.Check(err, IsNil)
   826  	c.Check(conn.SlotRef.Snap, Equals, "core")
   827  }
   828  
   829  // ResolveConnect detects lack of candidates
   830  func (s *RepositorySuite) TestResolveConnectNoImplicitCandidates(c *C) {
   831  	err := s.testRepo.AddInterface(&ifacetest.TestInterface{InterfaceName: "other-interface"})
   832  	c.Assert(err, IsNil)
   833  	// Tweak the "slot" slot so that it has an incompatible interface type.
   834  	s.coreSnap.Slots["slot"].Interface = "other-interface"
   835  	c.Assert(s.testRepo.AddSnap(s.coreSnap), IsNil)
   836  	c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
   837  	conn, err := s.testRepo.ResolveConnect("consumer", "plug", "", "")
   838  	c.Check(err, ErrorMatches, `snap "core" has no "interface" interface slots`)
   839  	c.Check(conn, IsNil)
   840  }
   841  
   842  // ResolveConnect detects ambiguities when slot snap name is empty
   843  func (s *RepositorySuite) TestResolveConnectAmbiguity(c *C) {
   844  	coreSnap := snaptest.MockInfo(c, `
   845  name: core
   846  version: 0
   847  type: os
   848  slots:
   849      slot-a:
   850          interface: interface
   851      slot-b:
   852          interface: interface
   853  `, nil)
   854  	c.Assert(s.testRepo.AddSnap(coreSnap), IsNil)
   855  	c.Assert(s.testRepo.AddSlot(s.slot), IsNil)
   856  	c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
   857  	conn, err := s.testRepo.ResolveConnect("consumer", "plug", "", "")
   858  	c.Check(err, ErrorMatches, `snap "core" has multiple "interface" interface slots: slot-a, slot-b`)
   859  	c.Check(conn, IsNil)
   860  }
   861  
   862  // Pug snap name cannot be empty
   863  func (s *RepositorySuite) TestResolveConnectEmptyPlugSnapName(c *C) {
   864  	conn, err := s.testRepo.ResolveConnect("", "plug", "producer", "slot")
   865  	c.Check(err, ErrorMatches, "cannot resolve connection, plug snap name is empty")
   866  	c.Check(conn, IsNil)
   867  }
   868  
   869  // Plug name cannot be empty
   870  func (s *RepositorySuite) TestResolveConnectEmptyPlugName(c *C) {
   871  	conn, err := s.testRepo.ResolveConnect("consumer", "", "producer", "slot")
   872  	c.Check(err, ErrorMatches, "cannot resolve connection, plug name is empty")
   873  	c.Check(conn, IsNil)
   874  }
   875  
   876  // Plug must exist
   877  func (s *RepositorySuite) TestResolveNoSuchPlug(c *C) {
   878  	conn, err := s.testRepo.ResolveConnect("consumer", "plug", "consumer", "slot")
   879  	c.Check(err, ErrorMatches, `snap "consumer" has no plug named "plug"`)
   880  	e, _ := err.(*NoPlugOrSlotError)
   881  	c.Check(e, NotNil)
   882  	c.Check(conn, IsNil)
   883  }
   884  
   885  // Slot snap name cannot be empty if there's no core snap around
   886  func (s *RepositorySuite) TestResolveConnectEmptySlotSnapName(c *C) {
   887  	c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
   888  	conn, err := s.testRepo.ResolveConnect("consumer", "plug", "", "slot")
   889  	c.Check(err, ErrorMatches, "cannot resolve connection, slot snap name is empty")
   890  	c.Check(conn, IsNil)
   891  }
   892  
   893  // Slot name cannot be empty if there's no core snap around
   894  func (s *RepositorySuite) TestResolveConnectEmptySlotName(c *C) {
   895  	c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
   896  	conn, err := s.testRepo.ResolveConnect("consumer", "plug", "producer", "")
   897  	c.Check(err, ErrorMatches, `snap "producer" has no "interface" interface slots`)
   898  	c.Check(conn, IsNil)
   899  }
   900  
   901  // Slot must exists
   902  func (s *RepositorySuite) TestResolveNoSuchSlot(c *C) {
   903  	c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
   904  	conn, err := s.testRepo.ResolveConnect("consumer", "plug", "producer", "slot")
   905  	c.Check(err, ErrorMatches, `snap "producer" has no slot named "slot"`)
   906  	e, _ := err.(*NoPlugOrSlotError)
   907  	c.Check(e, NotNil)
   908  	c.Check(conn, IsNil)
   909  }
   910  
   911  // Plug and slot must have matching types
   912  func (s *RepositorySuite) TestResolveIncompatibleTypes(c *C) {
   913  	c.Assert(s.testRepo.AddInterface(&ifacetest.TestInterface{InterfaceName: "other-interface"}), IsNil)
   914  	plug := &snap.PlugInfo{
   915  		Snap:      &snap.Info{SuggestedName: "consumer"},
   916  		Name:      "plug",
   917  		Interface: "other-interface",
   918  	}
   919  	c.Assert(s.testRepo.AddPlug(plug), IsNil)
   920  	c.Assert(s.testRepo.AddSlot(s.slot), IsNil)
   921  	// Connecting a plug to an incompatible slot fails with an appropriate error
   922  	conn, err := s.testRepo.ResolveConnect("consumer", "plug", "producer", "slot")
   923  	c.Check(err, ErrorMatches,
   924  		`cannot connect consumer:plug \("other-interface" interface\) to producer:slot \("interface" interface\)`)
   925  	c.Check(conn, IsNil)
   926  }
   927  
   928  // Tests for Repository.Connect()
   929  
   930  func (s *RepositorySuite) TestConnectFailsWhenPlugDoesNotExist(c *C) {
   931  	err := s.testRepo.AddSlot(s.slot)
   932  	c.Assert(err, IsNil)
   933  	// Connecting an unknown plug returns an appropriate error
   934  	connRef := NewConnRef(s.plug, s.slot)
   935  	_, err = s.testRepo.Connect(connRef, nil, nil, nil, nil, nil)
   936  	c.Assert(err, ErrorMatches, `cannot connect plug "plug" from snap "consumer": no such plug`)
   937  	e, _ := err.(*NoPlugOrSlotError)
   938  	c.Check(e, NotNil)
   939  }
   940  
   941  func (s *RepositorySuite) TestConnectFailsWhenSlotDoesNotExist(c *C) {
   942  	err := s.testRepo.AddPlug(s.plug)
   943  	c.Assert(err, IsNil)
   944  	// Connecting to an unknown slot returns an error
   945  	connRef := NewConnRef(s.plug, s.slot)
   946  	_, err = s.testRepo.Connect(connRef, nil, nil, nil, nil, nil)
   947  	c.Assert(err, ErrorMatches, `cannot connect slot "slot" from snap "producer": no such slot`)
   948  	e, _ := err.(*NoPlugOrSlotError)
   949  	c.Check(e, NotNil)
   950  }
   951  
   952  func (s *RepositorySuite) TestConnectSucceedsWhenIdenticalConnectExists(c *C) {
   953  	err := s.testRepo.AddPlug(s.plug)
   954  	c.Assert(err, IsNil)
   955  	err = s.testRepo.AddSlot(s.slot)
   956  	c.Assert(err, IsNil)
   957  	connRef := NewConnRef(s.plug, s.slot)
   958  	conn, err := s.testRepo.Connect(connRef, nil, nil, nil, nil, nil)
   959  	c.Assert(err, IsNil)
   960  	c.Assert(conn, NotNil)
   961  	c.Assert(conn.Plug, NotNil)
   962  	c.Assert(conn.Slot, NotNil)
   963  	c.Assert(conn.Plug.Name(), Equals, "plug")
   964  	c.Assert(conn.Slot.Name(), Equals, "slot")
   965  	// Connecting exactly the same thing twice succeeds without an error but does nothing.
   966  	_, err = s.testRepo.Connect(connRef, nil, nil, nil, nil, nil)
   967  	c.Assert(err, IsNil)
   968  	// Only one connection is actually present.
   969  	c.Assert(s.testRepo.Interfaces(), DeepEquals, &Interfaces{
   970  		Plugs:       []*snap.PlugInfo{s.plug},
   971  		Slots:       []*snap.SlotInfo{s.slot},
   972  		Connections: []*ConnRef{NewConnRef(s.plug, s.slot)},
   973  	})
   974  }
   975  
   976  func (s *RepositorySuite) TestConnectFailsWhenSlotAndPlugAreIncompatible(c *C) {
   977  	otherInterface := &ifacetest.TestInterface{InterfaceName: "other-interface"}
   978  	err := s.testRepo.AddInterface(otherInterface)
   979  	plug := &snap.PlugInfo{
   980  		Snap:      &snap.Info{SuggestedName: "consumer"},
   981  		Name:      "plug",
   982  		Interface: "other-interface",
   983  	}
   984  	c.Assert(err, IsNil)
   985  	err = s.testRepo.AddPlug(plug)
   986  	c.Assert(err, IsNil)
   987  	err = s.testRepo.AddSlot(s.slot)
   988  	c.Assert(err, IsNil)
   989  	// Connecting a plug to an incompatible slot fails with an appropriate error
   990  	connRef := NewConnRef(s.plug, s.slot)
   991  	_, err = s.testRepo.Connect(connRef, nil, nil, nil, nil, nil)
   992  	c.Assert(err, ErrorMatches, `cannot connect plug "consumer:plug" \(interface "other-interface"\) to "producer:slot" \(interface "interface"\)`)
   993  }
   994  
   995  func (s *RepositorySuite) TestConnectSucceeds(c *C) {
   996  	err := s.testRepo.AddPlug(s.plug)
   997  	c.Assert(err, IsNil)
   998  	err = s.testRepo.AddSlot(s.slot)
   999  	c.Assert(err, IsNil)
  1000  	// Connecting a plug works okay
  1001  	connRef := NewConnRef(s.plug, s.slot)
  1002  	_, err = s.testRepo.Connect(connRef, nil, nil, nil, nil, nil)
  1003  	c.Assert(err, IsNil)
  1004  }
  1005  
  1006  // Tests for Repository.Disconnect() and DisconnectAll()
  1007  
  1008  // Disconnect fails if any argument is empty
  1009  func (s *RepositorySuite) TestDisconnectFailsOnEmptyArgs(c *C) {
  1010  	err1 := s.testRepo.Disconnect(s.plug.Snap.InstanceName(), s.plug.Name, s.slot.Snap.InstanceName(), "")
  1011  	err2 := s.testRepo.Disconnect(s.plug.Snap.InstanceName(), s.plug.Name, "", s.slot.Name)
  1012  	err3 := s.testRepo.Disconnect(s.plug.Snap.InstanceName(), "", s.slot.Snap.InstanceName(), s.slot.Name)
  1013  	err4 := s.testRepo.Disconnect("", s.plug.Name, s.slot.Snap.InstanceName(), s.slot.Name)
  1014  	c.Assert(err1, ErrorMatches, `cannot disconnect, slot name is empty`)
  1015  	c.Assert(err2, ErrorMatches, `cannot disconnect, slot snap name is empty`)
  1016  	c.Assert(err3, ErrorMatches, `cannot disconnect, plug name is empty`)
  1017  	c.Assert(err4, ErrorMatches, `cannot disconnect, plug snap name is empty`)
  1018  }
  1019  
  1020  // Disconnect fails if plug doesn't exist
  1021  func (s *RepositorySuite) TestDisconnectFailsWithoutPlug(c *C) {
  1022  	c.Assert(s.testRepo.AddSlot(s.slot), IsNil)
  1023  	err := s.testRepo.Disconnect(s.plug.Snap.InstanceName(), s.plug.Name, s.slot.Snap.InstanceName(), s.slot.Name)
  1024  	c.Assert(err, ErrorMatches, `snap "consumer" has no plug named "plug"`)
  1025  	e, _ := err.(*NoPlugOrSlotError)
  1026  	c.Check(e, NotNil)
  1027  }
  1028  
  1029  // Disconnect fails if slot doesn't exist
  1030  func (s *RepositorySuite) TestDisconnectFailsWithutSlot(c *C) {
  1031  	c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
  1032  	err := s.testRepo.Disconnect(s.plug.Snap.InstanceName(), s.plug.Name, s.slot.Snap.InstanceName(), s.slot.Name)
  1033  	c.Assert(err, ErrorMatches, `snap "producer" has no slot named "slot"`)
  1034  	e, _ := err.(*NoPlugOrSlotError)
  1035  	c.Check(e, NotNil)
  1036  }
  1037  
  1038  // Disconnect fails if there's no connection to disconnect
  1039  func (s *RepositorySuite) TestDisconnectFailsWhenNotConnected(c *C) {
  1040  	c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
  1041  	c.Assert(s.testRepo.AddSlot(s.slot), IsNil)
  1042  	err := s.testRepo.Disconnect(s.plug.Snap.InstanceName(), s.plug.Name, s.slot.Snap.InstanceName(), s.slot.Name)
  1043  	c.Assert(err, ErrorMatches, `cannot disconnect consumer:plug from producer:slot, it is not connected`)
  1044  	e, _ := err.(*NotConnectedError)
  1045  	c.Check(e, NotNil)
  1046  }
  1047  
  1048  // Disconnect works when plug and slot exist and are connected
  1049  func (s *RepositorySuite) TestDisconnectSucceeds(c *C) {
  1050  	c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
  1051  	c.Assert(s.testRepo.AddSlot(s.slot), IsNil)
  1052  	_, err := s.testRepo.Connect(NewConnRef(s.plug, s.slot), nil, nil, nil, nil, nil)
  1053  	c.Assert(err, IsNil)
  1054  	_, err = s.testRepo.Connect(NewConnRef(s.plug, s.slot), nil, nil, nil, nil, nil)
  1055  	c.Assert(err, IsNil)
  1056  	err = s.testRepo.Disconnect(s.plug.Snap.InstanceName(), s.plug.Name, s.slot.Snap.InstanceName(), s.slot.Name)
  1057  	c.Assert(err, IsNil)
  1058  	c.Assert(s.testRepo.Interfaces(), DeepEquals, &Interfaces{
  1059  		Plugs: []*snap.PlugInfo{s.plug},
  1060  		Slots: []*snap.SlotInfo{s.slot},
  1061  	})
  1062  }
  1063  
  1064  // Tests for Repository.Connected
  1065  
  1066  // Connected fails if snap name is empty and there's no core snap around
  1067  func (s *RepositorySuite) TestConnectedFailsWithEmptySnapName(c *C) {
  1068  	_, err := s.testRepo.Connected("", s.plug.Name)
  1069  	c.Check(err, ErrorMatches, "internal error: cannot obtain core snap name while computing connections")
  1070  }
  1071  
  1072  // Connected fails if plug or slot name is empty
  1073  func (s *RepositorySuite) TestConnectedFailsWithEmptyPlugSlotName(c *C) {
  1074  	_, err := s.testRepo.Connected(s.plug.Snap.InstanceName(), "")
  1075  	c.Check(err, ErrorMatches, "plug or slot name is empty")
  1076  }
  1077  
  1078  // Connected fails if plug or slot doesn't exist
  1079  func (s *RepositorySuite) TestConnectedFailsWithoutPlugOrSlot(c *C) {
  1080  	_, err1 := s.testRepo.Connected(s.plug.Snap.InstanceName(), s.plug.Name)
  1081  	_, err2 := s.testRepo.Connected(s.slot.Snap.InstanceName(), s.slot.Name)
  1082  	c.Check(err1, ErrorMatches, `snap "consumer" has no plug or slot named "plug"`)
  1083  	e, _ := err1.(*NoPlugOrSlotError)
  1084  	c.Check(e, NotNil)
  1085  	c.Check(err2, ErrorMatches, `snap "producer" has no plug or slot named "slot"`)
  1086  	e, _ = err1.(*NoPlugOrSlotError)
  1087  	c.Check(e, NotNil)
  1088  }
  1089  
  1090  // Connected finds connections when asked from plug or from slot side
  1091  func (s *RepositorySuite) TestConnectedFindsConnections(c *C) {
  1092  	c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
  1093  	c.Assert(s.testRepo.AddSlot(s.slot), IsNil)
  1094  	_, err := s.testRepo.Connect(NewConnRef(s.plug, s.slot), nil, nil, nil, nil, nil)
  1095  	c.Assert(err, IsNil)
  1096  
  1097  	conns, err := s.testRepo.Connected(s.plug.Snap.InstanceName(), s.plug.Name)
  1098  	c.Assert(err, IsNil)
  1099  	c.Check(conns, DeepEquals, []*ConnRef{NewConnRef(s.plug, s.slot)})
  1100  
  1101  	conns, err = s.testRepo.Connected(s.slot.Snap.InstanceName(), s.slot.Name)
  1102  	c.Assert(err, IsNil)
  1103  	c.Check(conns, DeepEquals, []*ConnRef{NewConnRef(s.plug, s.slot)})
  1104  }
  1105  
  1106  // Connected uses the core snap if snap name is empty
  1107  func (s *RepositorySuite) TestConnectedFindsCoreSnap(c *C) {
  1108  	slot := &snap.SlotInfo{
  1109  		Snap:      &snap.Info{SuggestedName: "core", SnapType: snap.TypeOS},
  1110  		Name:      "slot",
  1111  		Interface: "interface",
  1112  	}
  1113  	c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
  1114  	c.Assert(s.testRepo.AddSlot(slot), IsNil)
  1115  	_, err := s.testRepo.Connect(NewConnRef(s.plug, slot), nil, nil, nil, nil, nil)
  1116  	c.Assert(err, IsNil)
  1117  
  1118  	conns, err := s.testRepo.Connected("", s.slot.Name)
  1119  	c.Assert(err, IsNil)
  1120  	c.Check(conns, DeepEquals, []*ConnRef{NewConnRef(s.plug, slot)})
  1121  }
  1122  
  1123  // Connected finds connections when asked from plug or from slot side
  1124  func (s *RepositorySuite) TestConnections(c *C) {
  1125  	c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
  1126  	c.Assert(s.testRepo.AddSlot(s.slot), IsNil)
  1127  	_, err := s.testRepo.Connect(NewConnRef(s.plug, s.slot), nil, nil, nil, nil, nil)
  1128  	c.Assert(err, IsNil)
  1129  
  1130  	conns, err := s.testRepo.Connections(s.plug.Snap.InstanceName())
  1131  	c.Assert(err, IsNil)
  1132  	c.Check(conns, DeepEquals, []*ConnRef{NewConnRef(s.plug, s.slot)})
  1133  
  1134  	conns, err = s.testRepo.Connections(s.slot.Snap.InstanceName())
  1135  	c.Assert(err, IsNil)
  1136  	c.Check(conns, DeepEquals, []*ConnRef{NewConnRef(s.plug, s.slot)})
  1137  
  1138  	conns, err = s.testRepo.Connections("abc")
  1139  	c.Assert(err, IsNil)
  1140  	c.Assert(conns, HasLen, 0)
  1141  }
  1142  
  1143  func (s *RepositorySuite) TestConnectionsWithSelfConnected(c *C) {
  1144  	c.Assert(s.testRepo.AddPlug(s.plugSelf), IsNil)
  1145  	c.Assert(s.testRepo.AddSlot(s.slot), IsNil)
  1146  	_, err := s.testRepo.Connect(NewConnRef(s.plugSelf, s.slot), nil, nil, nil, nil, nil)
  1147  	c.Assert(err, IsNil)
  1148  
  1149  	conns, err := s.testRepo.Connections(s.plugSelf.Snap.InstanceName())
  1150  	c.Assert(err, IsNil)
  1151  	c.Check(conns, DeepEquals, []*ConnRef{NewConnRef(s.plugSelf, s.slot)})
  1152  
  1153  	conns, err = s.testRepo.Connections(s.slot.Snap.InstanceName())
  1154  	c.Assert(err, IsNil)
  1155  	c.Check(conns, DeepEquals, []*ConnRef{NewConnRef(s.plugSelf, s.slot)})
  1156  }
  1157  
  1158  // Tests for Repository.DisconnectAll()
  1159  
  1160  func (s *RepositorySuite) TestDisconnectAll(c *C) {
  1161  	c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
  1162  	c.Assert(s.testRepo.AddSlot(s.slot), IsNil)
  1163  	_, err := s.testRepo.Connect(NewConnRef(s.plug, s.slot), nil, nil, nil, nil, nil)
  1164  	c.Assert(err, IsNil)
  1165  
  1166  	conns := []*ConnRef{NewConnRef(s.plug, s.slot)}
  1167  	s.testRepo.DisconnectAll(conns)
  1168  	c.Assert(s.testRepo.Interfaces(), DeepEquals, &Interfaces{
  1169  		Plugs: []*snap.PlugInfo{s.plug},
  1170  		Slots: []*snap.SlotInfo{s.slot},
  1171  	})
  1172  }
  1173  
  1174  // Tests for Repository.Interfaces()
  1175  
  1176  func (s *RepositorySuite) TestInterfacesSmokeTest(c *C) {
  1177  	err := s.testRepo.AddPlug(s.plug)
  1178  	c.Assert(err, IsNil)
  1179  	err = s.testRepo.AddSlot(s.slot)
  1180  	c.Assert(err, IsNil)
  1181  	// After connecting the result is as expected
  1182  	connRef := NewConnRef(s.plug, s.slot)
  1183  	_, err = s.testRepo.Connect(connRef, nil, nil, nil, nil, nil)
  1184  	c.Assert(err, IsNil)
  1185  	ifaces := s.testRepo.Interfaces()
  1186  	c.Assert(ifaces, DeepEquals, &Interfaces{
  1187  		Plugs:       []*snap.PlugInfo{s.plug},
  1188  		Slots:       []*snap.SlotInfo{s.slot},
  1189  		Connections: []*ConnRef{NewConnRef(s.plug, s.slot)},
  1190  	})
  1191  	// After disconnecting the connections become empty
  1192  	err = s.testRepo.Disconnect(s.plug.Snap.InstanceName(), s.plug.Name, s.slot.Snap.InstanceName(), s.slot.Name)
  1193  	c.Assert(err, IsNil)
  1194  	ifaces = s.testRepo.Interfaces()
  1195  	c.Assert(ifaces, DeepEquals, &Interfaces{
  1196  		Plugs: []*snap.PlugInfo{s.plug},
  1197  		Slots: []*snap.SlotInfo{s.slot},
  1198  	})
  1199  }
  1200  
  1201  // Tests for Repository.SnapSpecification
  1202  
  1203  const testSecurity SecuritySystem = "test"
  1204  
  1205  var testInterface = &ifacetest.TestInterface{
  1206  	InterfaceName: "interface",
  1207  	TestPermanentPlugCallback: func(spec *ifacetest.Specification, plug *snap.PlugInfo) error {
  1208  		spec.AddSnippet("static plug snippet")
  1209  		return nil
  1210  	},
  1211  	TestConnectedPlugCallback: func(spec *ifacetest.Specification, plug *ConnectedPlug, slot *ConnectedSlot) error {
  1212  		spec.AddSnippet("connection-specific plug snippet")
  1213  		return nil
  1214  	},
  1215  	TestPermanentSlotCallback: func(spec *ifacetest.Specification, slot *snap.SlotInfo) error {
  1216  		spec.AddSnippet("static slot snippet")
  1217  		return nil
  1218  	},
  1219  	TestConnectedSlotCallback: func(spec *ifacetest.Specification, plug *ConnectedPlug, slot *ConnectedSlot) error {
  1220  		spec.AddSnippet("connection-specific slot snippet")
  1221  		return nil
  1222  	},
  1223  }
  1224  
  1225  func (s *RepositorySuite) TestSnapSpecification(c *C) {
  1226  	repo := s.emptyRepo
  1227  	backend := &ifacetest.TestSecurityBackend{BackendName: testSecurity}
  1228  	c.Assert(repo.AddBackend(backend), IsNil)
  1229  	c.Assert(repo.AddInterface(testInterface), IsNil)
  1230  	c.Assert(repo.AddPlug(s.plug), IsNil)
  1231  	c.Assert(repo.AddSlot(s.slot), IsNil)
  1232  
  1233  	// Snaps should get static security now
  1234  	spec, err := repo.SnapSpecification(testSecurity, s.plug.Snap.InstanceName())
  1235  	c.Assert(err, IsNil)
  1236  	c.Check(spec.(*ifacetest.Specification).Snippets, DeepEquals, []string{"static plug snippet"})
  1237  
  1238  	spec, err = repo.SnapSpecification(testSecurity, s.slot.Snap.InstanceName())
  1239  	c.Assert(err, IsNil)
  1240  	c.Check(spec.(*ifacetest.Specification).Snippets, DeepEquals, []string{"static slot snippet"})
  1241  
  1242  	// Establish connection between plug and slot
  1243  	connRef := NewConnRef(s.plug, s.slot)
  1244  	_, err = repo.Connect(connRef, nil, nil, nil, nil, nil)
  1245  	c.Assert(err, IsNil)
  1246  
  1247  	// Snaps should get static and connection-specific security now
  1248  	spec, err = repo.SnapSpecification(testSecurity, s.plug.Snap.InstanceName())
  1249  	c.Assert(err, IsNil)
  1250  	c.Check(spec.(*ifacetest.Specification).Snippets, DeepEquals, []string{
  1251  		"static plug snippet",
  1252  		"connection-specific plug snippet",
  1253  	})
  1254  
  1255  	spec, err = repo.SnapSpecification(testSecurity, s.slot.Snap.InstanceName())
  1256  	c.Assert(err, IsNil)
  1257  	c.Check(spec.(*ifacetest.Specification).Snippets, DeepEquals, []string{
  1258  		"static slot snippet",
  1259  		"connection-specific slot snippet",
  1260  	})
  1261  }
  1262  
  1263  func (s *RepositorySuite) TestSnapSpecificationFailureWithConnectionSnippets(c *C) {
  1264  	var testSecurity SecuritySystem = "security"
  1265  	backend := &ifacetest.TestSecurityBackend{BackendName: testSecurity}
  1266  	iface := &ifacetest.TestInterface{
  1267  		InterfaceName: "interface",
  1268  		TestConnectedSlotCallback: func(spec *ifacetest.Specification, plug *ConnectedPlug, slot *ConnectedSlot) error {
  1269  			return fmt.Errorf("cannot compute snippet for provider")
  1270  		},
  1271  		TestConnectedPlugCallback: func(spec *ifacetest.Specification, plug *ConnectedPlug, slot *ConnectedSlot) error {
  1272  			return fmt.Errorf("cannot compute snippet for consumer")
  1273  		},
  1274  	}
  1275  	repo := s.emptyRepo
  1276  
  1277  	c.Assert(repo.AddBackend(backend), IsNil)
  1278  	c.Assert(repo.AddInterface(iface), IsNil)
  1279  	c.Assert(repo.AddPlug(s.plug), IsNil)
  1280  	c.Assert(repo.AddSlot(s.slot), IsNil)
  1281  	connRef := NewConnRef(s.plug, s.slot)
  1282  	_, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
  1283  	c.Assert(err, IsNil)
  1284  
  1285  	spec, err := repo.SnapSpecification(testSecurity, s.plug.Snap.InstanceName())
  1286  	c.Assert(err, ErrorMatches, "cannot compute snippet for consumer")
  1287  	c.Assert(spec, IsNil)
  1288  
  1289  	spec, err = repo.SnapSpecification(testSecurity, s.slot.Snap.InstanceName())
  1290  	c.Assert(err, ErrorMatches, "cannot compute snippet for provider")
  1291  	c.Assert(spec, IsNil)
  1292  }
  1293  
  1294  func (s *RepositorySuite) TestSnapSpecificationFailureWithPermanentSnippets(c *C) {
  1295  	var testSecurity SecuritySystem = "security"
  1296  	iface := &ifacetest.TestInterface{
  1297  		InterfaceName: "interface",
  1298  		TestPermanentSlotCallback: func(spec *ifacetest.Specification, slot *snap.SlotInfo) error {
  1299  			return fmt.Errorf("cannot compute snippet for provider")
  1300  		},
  1301  		TestPermanentPlugCallback: func(spec *ifacetest.Specification, plug *snap.PlugInfo) error {
  1302  			return fmt.Errorf("cannot compute snippet for consumer")
  1303  		},
  1304  	}
  1305  	backend := &ifacetest.TestSecurityBackend{BackendName: testSecurity}
  1306  	repo := s.emptyRepo
  1307  	c.Assert(repo.AddBackend(backend), IsNil)
  1308  	c.Assert(repo.AddInterface(iface), IsNil)
  1309  	c.Assert(repo.AddPlug(s.plug), IsNil)
  1310  	c.Assert(repo.AddSlot(s.slot), IsNil)
  1311  	connRef := NewConnRef(s.plug, s.slot)
  1312  	_, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
  1313  	c.Assert(err, IsNil)
  1314  
  1315  	spec, err := repo.SnapSpecification(testSecurity, s.plug.Snap.InstanceName())
  1316  	c.Assert(err, ErrorMatches, "cannot compute snippet for consumer")
  1317  	c.Assert(spec, IsNil)
  1318  
  1319  	spec, err = repo.SnapSpecification(testSecurity, s.slot.Snap.InstanceName())
  1320  	c.Assert(err, ErrorMatches, "cannot compute snippet for provider")
  1321  	c.Assert(spec, IsNil)
  1322  }
  1323  
  1324  type testSideArity struct {
  1325  	sideSnapName string
  1326  }
  1327  
  1328  func (a *testSideArity) SlotsPerPlugAny() bool {
  1329  	return strings.HasSuffix(a.sideSnapName, "2")
  1330  }
  1331  
  1332  func (s *RepositorySuite) TestAutoConnectCandidatePlugsAndSlots(c *C) {
  1333  	// Add two interfaces, one with automatic connections, one with manual
  1334  	repo := s.emptyRepo
  1335  	err := repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "auto"})
  1336  	c.Assert(err, IsNil)
  1337  	err = repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "manual"})
  1338  	c.Assert(err, IsNil)
  1339  
  1340  	policyCheck := func(plug *ConnectedPlug, slot *ConnectedSlot) (bool, SideArity, error) {
  1341  		return slot.Interface() == "auto", &testSideArity{plug.Snap().InstanceName()}, nil
  1342  	}
  1343  
  1344  	// Add a pair of snaps with plugs/slots using those two interfaces
  1345  	consumer := snaptest.MockInfo(c, `
  1346  name: consumer
  1347  version: 0
  1348  plugs:
  1349      auto:
  1350      manual:
  1351  `, nil)
  1352  	producer := snaptest.MockInfo(c, `
  1353  name: producer
  1354  version: 0
  1355  type: os
  1356  slots:
  1357      auto:
  1358      manual:
  1359  `, nil)
  1360  	err = repo.AddSnap(producer)
  1361  	c.Assert(err, IsNil)
  1362  	err = repo.AddSnap(consumer)
  1363  	c.Assert(err, IsNil)
  1364  
  1365  	candidateSlots, arities := repo.AutoConnectCandidateSlots("consumer", "auto", policyCheck)
  1366  	c.Assert(candidateSlots, HasLen, 1)
  1367  	c.Check(candidateSlots[0].Snap.InstanceName(), Equals, "producer")
  1368  	c.Check(candidateSlots[0].Interface, Equals, "auto")
  1369  	c.Check(candidateSlots[0].Name, Equals, "auto")
  1370  	c.Assert(arities, HasLen, 1)
  1371  	c.Check(arities[0].SlotsPerPlugAny(), Equals, false)
  1372  
  1373  	candidatePlugs := repo.AutoConnectCandidatePlugs("producer", "auto", policyCheck)
  1374  	c.Assert(candidatePlugs, HasLen, 1)
  1375  	c.Check(candidatePlugs[0].Snap.InstanceName(), Equals, "consumer")
  1376  	c.Check(candidatePlugs[0].Interface, Equals, "auto")
  1377  	c.Check(candidatePlugs[0].Name, Equals, "auto")
  1378  }
  1379  
  1380  func (s *RepositorySuite) TestAutoConnectCandidatePlugsAndSlotsSymmetry(c *C) {
  1381  	repo := s.emptyRepo
  1382  	// Add a "auto" interface
  1383  	err := repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "auto"})
  1384  	c.Assert(err, IsNil)
  1385  
  1386  	policyCheck := func(plug *ConnectedPlug, slot *ConnectedSlot) (bool, SideArity, error) {
  1387  		return slot.Interface() == "auto", &testSideArity{plug.Snap().InstanceName()}, nil
  1388  	}
  1389  
  1390  	// Add a producer snap for "auto"
  1391  	producer := snaptest.MockInfo(c, `
  1392  name: producer
  1393  version: 0
  1394  type: os
  1395  slots:
  1396      auto:
  1397  `, nil)
  1398  	err = repo.AddSnap(producer)
  1399  	c.Assert(err, IsNil)
  1400  
  1401  	// Add two consumers snaps for "auto"
  1402  	consumer1 := snaptest.MockInfo(c, `
  1403  name: consumer1
  1404  version: 0
  1405  plugs:
  1406      auto:
  1407  `, nil)
  1408  
  1409  	err = repo.AddSnap(consumer1)
  1410  	c.Assert(err, IsNil)
  1411  
  1412  	// Add two consumers snaps for "auto"
  1413  	consumer2 := snaptest.MockInfo(c, `
  1414  name: consumer2
  1415  version: 0
  1416  plugs:
  1417      auto:
  1418  `, nil)
  1419  
  1420  	err = repo.AddSnap(consumer2)
  1421  	c.Assert(err, IsNil)
  1422  
  1423  	// Both can auto-connect
  1424  	candidateSlots, arities := repo.AutoConnectCandidateSlots("consumer1", "auto", policyCheck)
  1425  	c.Assert(candidateSlots, HasLen, 1)
  1426  	c.Check(candidateSlots[0].Snap.InstanceName(), Equals, "producer")
  1427  	c.Check(candidateSlots[0].Interface, Equals, "auto")
  1428  	c.Check(candidateSlots[0].Name, Equals, "auto")
  1429  	c.Assert(arities, HasLen, 1)
  1430  	c.Check(arities[0].SlotsPerPlugAny(), Equals, false)
  1431  
  1432  	candidateSlots, arities = repo.AutoConnectCandidateSlots("consumer2", "auto", policyCheck)
  1433  	c.Assert(candidateSlots, HasLen, 1)
  1434  	c.Check(candidateSlots[0].Snap.InstanceName(), Equals, "producer")
  1435  	c.Check(candidateSlots[0].Interface, Equals, "auto")
  1436  	c.Check(candidateSlots[0].Name, Equals, "auto")
  1437  	c.Assert(arities, HasLen, 1)
  1438  	c.Check(arities[0].SlotsPerPlugAny(), Equals, true)
  1439  
  1440  	// Plugs candidates seen from the producer (for example if
  1441  	// it's installed after) should be the same
  1442  	candidatePlugs := repo.AutoConnectCandidatePlugs("producer", "auto", policyCheck)
  1443  	c.Assert(candidatePlugs, HasLen, 2)
  1444  }
  1445  
  1446  func (s *RepositorySuite) TestAutoConnectCandidateSlotsSideArity(c *C) {
  1447  	repo := s.emptyRepo
  1448  	// Add a "auto" interface
  1449  	err := repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "auto"})
  1450  	c.Assert(err, IsNil)
  1451  
  1452  	policyCheck := func(plug *ConnectedPlug, slot *ConnectedSlot) (bool, SideArity, error) {
  1453  		return slot.Interface() == "auto", &testSideArity{slot.Snap().InstanceName()}, nil
  1454  	}
  1455  
  1456  	// Add two producer snaps for "auto"
  1457  	producer1 := snaptest.MockInfo(c, `
  1458  name: producer1
  1459  version: 0
  1460  slots:
  1461      auto:
  1462  `, nil)
  1463  	err = repo.AddSnap(producer1)
  1464  	c.Assert(err, IsNil)
  1465  
  1466  	producer2 := snaptest.MockInfo(c, `
  1467  name: producer2
  1468  version: 0
  1469  slots:
  1470      auto:
  1471  `, nil)
  1472  	err = repo.AddSnap(producer2)
  1473  	c.Assert(err, IsNil)
  1474  
  1475  	// Add a consumer snap for "auto"
  1476  	consumer := snaptest.MockInfo(c, `
  1477  name: consumer
  1478  version: 0
  1479  plugs:
  1480      auto:
  1481  `, nil)
  1482  	err = repo.AddSnap(consumer)
  1483  	c.Assert(err, IsNil)
  1484  
  1485  	// Both slots could auto-connect
  1486  	seenProducers := make(map[string]bool)
  1487  	candidateSlots, arities := repo.AutoConnectCandidateSlots("consumer", "auto", policyCheck)
  1488  	c.Assert(candidateSlots, HasLen, 2)
  1489  	c.Assert(arities, HasLen, 2)
  1490  	for i, candSlot := range candidateSlots {
  1491  		c.Check(candSlot.Interface, Equals, "auto")
  1492  		c.Check(candSlot.Name, Equals, "auto")
  1493  		producerName := candSlot.Snap.InstanceName()
  1494  		// SideArities match
  1495  		switch producerName {
  1496  		case "producer1":
  1497  			c.Check(arities[i].SlotsPerPlugAny(), Equals, false)
  1498  		case "producer2":
  1499  			c.Check(arities[i].SlotsPerPlugAny(), Equals, true)
  1500  		}
  1501  		seenProducers[producerName] = true
  1502  	}
  1503  	c.Check(seenProducers, DeepEquals, map[string]bool{
  1504  		"producer1": true,
  1505  		"producer2": true,
  1506  	})
  1507  }
  1508  
  1509  // Tests for AddSnap and RemoveSnap
  1510  
  1511  type AddRemoveSuite struct {
  1512  	testutil.BaseTest
  1513  	repo *Repository
  1514  }
  1515  
  1516  var _ = Suite(&AddRemoveSuite{})
  1517  
  1518  func (s *AddRemoveSuite) SetUpTest(c *C) {
  1519  	s.BaseTest.SetUpTest(c)
  1520  	s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
  1521  
  1522  	s.repo = NewRepository()
  1523  	err := s.repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "iface"})
  1524  	c.Assert(err, IsNil)
  1525  	err = s.repo.AddInterface(&ifacetest.TestInterface{
  1526  		InterfaceName:             "invalid",
  1527  		BeforePreparePlugCallback: func(plug *snap.PlugInfo) error { return fmt.Errorf("plug is invalid") },
  1528  		BeforePrepareSlotCallback: func(slot *snap.SlotInfo) error { return fmt.Errorf("slot is invalid") },
  1529  	})
  1530  	c.Assert(err, IsNil)
  1531  }
  1532  
  1533  func (s *AddRemoveSuite) TearDownTest(c *C) {
  1534  	s.BaseTest.TearDownTest(c)
  1535  }
  1536  
  1537  const testConsumerYaml = `
  1538  name: consumer
  1539  version: 0
  1540  apps:
  1541      app:
  1542          plugs: [iface]
  1543  `
  1544  const testProducerYaml = `
  1545  name: producer
  1546  version: 0
  1547  apps:
  1548      app:
  1549          slots: [iface]
  1550  `
  1551  
  1552  func (s *AddRemoveSuite) addSnap(c *C, yaml string) (*snap.Info, error) {
  1553  	snapInfo := snaptest.MockInfo(c, yaml, nil)
  1554  	return snapInfo, s.repo.AddSnap(snapInfo)
  1555  }
  1556  
  1557  func (s *AddRemoveSuite) TestAddSnapAddsPlugs(c *C) {
  1558  	_, err := s.addSnap(c, testConsumerYaml)
  1559  	c.Assert(err, IsNil)
  1560  	// The plug was added
  1561  	c.Assert(s.repo.Plug("consumer", "iface"), Not(IsNil))
  1562  }
  1563  
  1564  func (s *AddRemoveSuite) TestAddSnapErrorsOnExistingSnapPlugs(c *C) {
  1565  	_, err := s.addSnap(c, testConsumerYaml)
  1566  	c.Assert(err, IsNil)
  1567  	_, err = s.addSnap(c, testConsumerYaml)
  1568  	c.Assert(err, ErrorMatches, `cannot register interfaces for snap "consumer" more than once`)
  1569  }
  1570  
  1571  func (s *AddRemoveSuite) TestAddSnapAddsSlots(c *C) {
  1572  	_, err := s.addSnap(c, testProducerYaml)
  1573  	c.Assert(err, IsNil)
  1574  	// The slot was added
  1575  	c.Assert(s.repo.Slot("producer", "iface"), Not(IsNil))
  1576  }
  1577  
  1578  func (s *AddRemoveSuite) TestAddSnapErrorsOnExistingSnapSlots(c *C) {
  1579  	_, err := s.addSnap(c, testProducerYaml)
  1580  	c.Assert(err, IsNil)
  1581  	_, err = s.addSnap(c, testProducerYaml)
  1582  	c.Assert(err, ErrorMatches, `cannot register interfaces for snap "producer" more than once`)
  1583  }
  1584  
  1585  func (s *AddRemoveSuite) TestAddSnapSkipsUnknownInterfaces(c *C) {
  1586  	info, err := s.addSnap(c, `
  1587  name: bogus
  1588  version: 0
  1589  plugs:
  1590    bogus-plug:
  1591  slots:
  1592    bogus-slot:
  1593  `)
  1594  	c.Assert(err, IsNil)
  1595  	// the snap knowns about the bogus plug and slot
  1596  	c.Assert(info.Plugs["bogus-plug"], NotNil)
  1597  	c.Assert(info.Slots["bogus-slot"], NotNil)
  1598  	// but the repository ignores them
  1599  	c.Assert(s.repo.Plug("bogus", "bogus-plug"), IsNil)
  1600  	c.Assert(s.repo.Slot("bogus", "bogus-slot"), IsNil)
  1601  }
  1602  
  1603  func (s AddRemoveSuite) TestRemoveRemovesPlugs(c *C) {
  1604  	_, err := s.addSnap(c, testConsumerYaml)
  1605  	c.Assert(err, IsNil)
  1606  	s.repo.RemoveSnap("consumer")
  1607  	c.Assert(s.repo.Plug("consumer", "iface"), IsNil)
  1608  }
  1609  
  1610  func (s AddRemoveSuite) TestRemoveRemovesSlots(c *C) {
  1611  	_, err := s.addSnap(c, testProducerYaml)
  1612  	c.Assert(err, IsNil)
  1613  	s.repo.RemoveSnap("producer")
  1614  	c.Assert(s.repo.Plug("producer", "iface"), IsNil)
  1615  }
  1616  
  1617  func (s *AddRemoveSuite) TestRemoveSnapErrorsOnStillConnectedPlug(c *C) {
  1618  	_, err := s.addSnap(c, testConsumerYaml)
  1619  	c.Assert(err, IsNil)
  1620  	_, err = s.addSnap(c, testProducerYaml)
  1621  	c.Assert(err, IsNil)
  1622  	connRef := &ConnRef{PlugRef: PlugRef{Snap: "consumer", Name: "iface"}, SlotRef: SlotRef{Snap: "producer", Name: "iface"}}
  1623  	_, err = s.repo.Connect(connRef, nil, nil, nil, nil, nil)
  1624  	c.Assert(err, IsNil)
  1625  	err = s.repo.RemoveSnap("consumer")
  1626  	c.Assert(err, ErrorMatches, "cannot remove connected plug consumer.iface")
  1627  }
  1628  
  1629  func (s *AddRemoveSuite) TestRemoveSnapErrorsOnStillConnectedSlot(c *C) {
  1630  	_, err := s.addSnap(c, testConsumerYaml)
  1631  	c.Assert(err, IsNil)
  1632  	_, err = s.addSnap(c, testProducerYaml)
  1633  	c.Assert(err, IsNil)
  1634  	connRef := &ConnRef{PlugRef: PlugRef{Snap: "consumer", Name: "iface"}, SlotRef: SlotRef{Snap: "producer", Name: "iface"}}
  1635  	_, err = s.repo.Connect(connRef, nil, nil, nil, nil, nil)
  1636  	c.Assert(err, IsNil)
  1637  	err = s.repo.RemoveSnap("producer")
  1638  	c.Assert(err, ErrorMatches, "cannot remove connected slot producer.iface")
  1639  }
  1640  
  1641  type DisconnectSnapSuite struct {
  1642  	testutil.BaseTest
  1643  	repo               *Repository
  1644  	s1, s2, s2Instance *snap.Info
  1645  }
  1646  
  1647  var _ = Suite(&DisconnectSnapSuite{})
  1648  
  1649  func (s *DisconnectSnapSuite) SetUpTest(c *C) {
  1650  	s.BaseTest.SetUpTest(c)
  1651  	s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
  1652  
  1653  	s.repo = NewRepository()
  1654  
  1655  	err := s.repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "iface-a"})
  1656  	c.Assert(err, IsNil)
  1657  	err = s.repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "iface-b"})
  1658  	c.Assert(err, IsNil)
  1659  
  1660  	s.s1 = snaptest.MockInfo(c, `
  1661  name: s1
  1662  version: 0
  1663  plugs:
  1664      iface-a:
  1665  slots:
  1666      iface-b:
  1667  `, nil)
  1668  	err = s.repo.AddSnap(s.s1)
  1669  	c.Assert(err, IsNil)
  1670  
  1671  	s.s2 = snaptest.MockInfo(c, `
  1672  name: s2
  1673  version: 0
  1674  plugs:
  1675      iface-b:
  1676  slots:
  1677      iface-a:
  1678  `, nil)
  1679  	c.Assert(err, IsNil)
  1680  	err = s.repo.AddSnap(s.s2)
  1681  	c.Assert(err, IsNil)
  1682  	s.s2Instance = snaptest.MockInfo(c, `
  1683  name: s2
  1684  version: 0
  1685  plugs:
  1686      iface-b:
  1687  slots:
  1688      iface-a:
  1689  `, nil)
  1690  	s.s2Instance.InstanceKey = "instance"
  1691  	c.Assert(err, IsNil)
  1692  	err = s.repo.AddSnap(s.s2Instance)
  1693  	c.Assert(err, IsNil)
  1694  }
  1695  
  1696  func (s *DisconnectSnapSuite) TearDownTest(c *C) {
  1697  	s.BaseTest.TearDownTest(c)
  1698  }
  1699  
  1700  func (s *DisconnectSnapSuite) TestNotConnected(c *C) {
  1701  	affected, err := s.repo.DisconnectSnap("s1")
  1702  	c.Assert(err, IsNil)
  1703  	c.Check(affected, HasLen, 0)
  1704  }
  1705  
  1706  func (s *DisconnectSnapSuite) TestOutgoingConnection(c *C) {
  1707  	connRef := &ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "iface-a"}, SlotRef: SlotRef{Snap: "s2", Name: "iface-a"}}
  1708  	_, err := s.repo.Connect(connRef, nil, nil, nil, nil, nil)
  1709  	c.Assert(err, IsNil)
  1710  	// Disconnect s1 with which has an outgoing connection to s2
  1711  	affected, err := s.repo.DisconnectSnap("s1")
  1712  	c.Assert(err, IsNil)
  1713  	c.Check(affected, testutil.Contains, "s1")
  1714  	c.Check(affected, testutil.Contains, "s2")
  1715  }
  1716  
  1717  func (s *DisconnectSnapSuite) TestIncomingConnection(c *C) {
  1718  	connRef := &ConnRef{PlugRef: PlugRef{Snap: "s2", Name: "iface-b"}, SlotRef: SlotRef{Snap: "s1", Name: "iface-b"}}
  1719  	_, err := s.repo.Connect(connRef, nil, nil, nil, nil, nil)
  1720  	c.Assert(err, IsNil)
  1721  	// Disconnect s1 with which has an incoming connection from s2
  1722  	affected, err := s.repo.DisconnectSnap("s1")
  1723  	c.Assert(err, IsNil)
  1724  	c.Check(affected, testutil.Contains, "s1")
  1725  	c.Check(affected, testutil.Contains, "s2")
  1726  }
  1727  
  1728  func (s *DisconnectSnapSuite) TestCrossConnection(c *C) {
  1729  	// This test is symmetric wrt s1 <-> s2 connections
  1730  	for _, snapName := range []string{"s1", "s2"} {
  1731  		connRef1 := &ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "iface-a"}, SlotRef: SlotRef{Snap: "s2", Name: "iface-a"}}
  1732  		_, err := s.repo.Connect(connRef1, nil, nil, nil, nil, nil)
  1733  		c.Assert(err, IsNil)
  1734  		connRef2 := &ConnRef{PlugRef: PlugRef{Snap: "s2", Name: "iface-b"}, SlotRef: SlotRef{Snap: "s1", Name: "iface-b"}}
  1735  		_, err = s.repo.Connect(connRef2, nil, nil, nil, nil, nil)
  1736  		c.Assert(err, IsNil)
  1737  		affected, err := s.repo.DisconnectSnap(snapName)
  1738  		c.Assert(err, IsNil)
  1739  		c.Check(affected, testutil.Contains, "s1")
  1740  		c.Check(affected, testutil.Contains, "s2")
  1741  	}
  1742  }
  1743  
  1744  func (s *DisconnectSnapSuite) TestParallelInstances(c *C) {
  1745  	_, err := s.repo.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "iface-a"}, SlotRef: SlotRef{Snap: "s2_instance", Name: "iface-a"}}, nil, nil, nil, nil, nil)
  1746  	c.Assert(err, IsNil)
  1747  	affected, err := s.repo.DisconnectSnap("s1")
  1748  	c.Assert(err, IsNil)
  1749  	c.Check(affected, testutil.Contains, "s1")
  1750  	c.Check(affected, testutil.Contains, "s2_instance")
  1751  
  1752  	_, err = s.repo.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s2_instance", Name: "iface-b"}, SlotRef: SlotRef{Snap: "s1", Name: "iface-b"}}, nil, nil, nil, nil, nil)
  1753  	c.Assert(err, IsNil)
  1754  	affected, err = s.repo.DisconnectSnap("s1")
  1755  	c.Assert(err, IsNil)
  1756  	c.Check(affected, testutil.Contains, "s1")
  1757  	c.Check(affected, testutil.Contains, "s2_instance")
  1758  }
  1759  
  1760  func contentPolicyCheck(plug *ConnectedPlug, slot *ConnectedSlot) (bool, SideArity, error) {
  1761  	return plug.Snap().Publisher.ID == slot.Snap().Publisher.ID, nil, nil
  1762  }
  1763  
  1764  func contentAutoConnect(plug *snap.PlugInfo, slot *snap.SlotInfo) bool {
  1765  	return plug.Attrs["content"] == slot.Attrs["content"]
  1766  }
  1767  
  1768  // internal helper that creates a new repository with two snaps, one
  1769  // is a content plug and one a content slot
  1770  func makeContentConnectionTestSnaps(c *C, plugContentToken, slotContentToken string) (*Repository, *snap.Info, *snap.Info) {
  1771  	repo := NewRepository()
  1772  	err := repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "content", AutoConnectCallback: contentAutoConnect})
  1773  	c.Assert(err, IsNil)
  1774  
  1775  	plugSnap := snaptest.MockInfo(c, fmt.Sprintf(`
  1776  name: content-plug-snap
  1777  version: 0
  1778  plugs:
  1779    imported-content:
  1780      interface: content
  1781      content: %s
  1782  `, plugContentToken), nil)
  1783  	slotSnap := snaptest.MockInfo(c, fmt.Sprintf(`
  1784  name: content-slot-snap
  1785  version: 0
  1786  slots:
  1787    exported-content:
  1788      interface: content
  1789      content: %s
  1790  `, slotContentToken), nil)
  1791  
  1792  	err = repo.AddSnap(plugSnap)
  1793  	c.Assert(err, IsNil)
  1794  	err = repo.AddSnap(slotSnap)
  1795  	c.Assert(err, IsNil)
  1796  
  1797  	return repo, plugSnap, slotSnap
  1798  }
  1799  
  1800  func (s *RepositorySuite) TestAutoConnectContentInterfaceSimple(c *C) {
  1801  	repo, _, _ := makeContentConnectionTestSnaps(c, "mylib", "mylib")
  1802  	candidateSlots, _ := repo.AutoConnectCandidateSlots("content-plug-snap", "imported-content", contentPolicyCheck)
  1803  	c.Assert(candidateSlots, HasLen, 1)
  1804  	c.Check(candidateSlots[0].Name, Equals, "exported-content")
  1805  	candidatePlugs := repo.AutoConnectCandidatePlugs("content-slot-snap", "exported-content", contentPolicyCheck)
  1806  	c.Assert(candidatePlugs, HasLen, 1)
  1807  	c.Check(candidatePlugs[0].Name, Equals, "imported-content")
  1808  }
  1809  
  1810  func (s *RepositorySuite) TestAutoConnectContentInterfaceOSWorksCorrectly(c *C) {
  1811  	repo, _, slotSnap := makeContentConnectionTestSnaps(c, "mylib", "otherlib")
  1812  	slotSnap.SnapType = snap.TypeOS
  1813  
  1814  	candidateSlots, _ := repo.AutoConnectCandidateSlots("content-plug-snap", "imported-content", contentPolicyCheck)
  1815  	c.Check(candidateSlots, HasLen, 0)
  1816  	candidatePlugs := repo.AutoConnectCandidatePlugs("content-slot-snap", "exported-content", contentPolicyCheck)
  1817  	c.Assert(candidatePlugs, HasLen, 0)
  1818  }
  1819  
  1820  func (s *RepositorySuite) TestAutoConnectContentInterfaceNoMatchingContent(c *C) {
  1821  	repo, _, _ := makeContentConnectionTestSnaps(c, "mylib", "otherlib")
  1822  	candidateSlots, _ := repo.AutoConnectCandidateSlots("content-plug-snap", "imported-content", contentPolicyCheck)
  1823  	c.Check(candidateSlots, HasLen, 0)
  1824  	candidatePlugs := repo.AutoConnectCandidatePlugs("content-slot-snap", "exported-content", contentPolicyCheck)
  1825  	c.Assert(candidatePlugs, HasLen, 0)
  1826  }
  1827  
  1828  func (s *RepositorySuite) TestAutoConnectContentInterfaceNoMatchingDeveloper(c *C) {
  1829  	repo, plugSnap, slotSnap := makeContentConnectionTestSnaps(c, "mylib", "mylib")
  1830  	// real code will use the assertions, this is just for emulation
  1831  	plugSnap.Publisher.ID = "fooid"
  1832  	slotSnap.Publisher.ID = "barid"
  1833  
  1834  	candidateSlots, _ := repo.AutoConnectCandidateSlots("content-plug-snap", "imported-content", contentPolicyCheck)
  1835  	c.Check(candidateSlots, HasLen, 0)
  1836  	candidatePlugs := repo.AutoConnectCandidatePlugs("content-slot-snap", "exported-content", contentPolicyCheck)
  1837  	c.Assert(candidatePlugs, HasLen, 0)
  1838  }
  1839  
  1840  func (s *RepositorySuite) TestInfo(c *C) {
  1841  	r := s.emptyRepo
  1842  
  1843  	// Add some test interfaces.
  1844  	i1 := &ifacetest.TestInterface{InterfaceName: "i1", InterfaceStaticInfo: StaticInfo{Summary: "i1 summary", DocURL: "http://example.com/i1"}}
  1845  	i2 := &ifacetest.TestInterface{InterfaceName: "i2", InterfaceStaticInfo: StaticInfo{Summary: "i2 summary", DocURL: "http://example.com/i2"}}
  1846  	i3 := &ifacetest.TestInterface{InterfaceName: "i3", InterfaceStaticInfo: StaticInfo{Summary: "i3 summary", DocURL: "http://example.com/i3"}}
  1847  	c.Assert(r.AddInterface(i1), IsNil)
  1848  	c.Assert(r.AddInterface(i2), IsNil)
  1849  	c.Assert(r.AddInterface(i3), IsNil)
  1850  
  1851  	// Add some test snaps.
  1852  	s1 := snaptest.MockInfo(c, `
  1853  name: s1
  1854  version: 0
  1855  apps:
  1856    s1:
  1857      plugs: [i1, i2]
  1858  `, nil)
  1859  	c.Assert(r.AddSnap(s1), IsNil)
  1860  
  1861  	s2 := snaptest.MockInfo(c, `
  1862  name: s2
  1863  version: 0
  1864  apps:
  1865    s2:
  1866      slots: [i1, i3]
  1867  `, nil)
  1868  	c.Assert(r.AddSnap(s2), IsNil)
  1869  
  1870  	s3 := snaptest.MockInfo(c, `
  1871  name: s3
  1872  version: 0
  1873  type: os
  1874  slots:
  1875    i2:
  1876  `, nil)
  1877  	c.Assert(r.AddSnap(s3), IsNil)
  1878  	s3Instance := snaptest.MockInfo(c, `
  1879  name: s3
  1880  version: 0
  1881  type: os
  1882  slots:
  1883    i2:
  1884  `, nil)
  1885  	s3Instance.InstanceKey = "instance"
  1886  	c.Assert(r.AddSnap(s3Instance), IsNil)
  1887  	s4 := snaptest.MockInfo(c, `
  1888  name: s4
  1889  version: 0
  1890  apps:
  1891    s1:
  1892      plugs: [i2]
  1893  `, nil)
  1894  	c.Assert(r.AddSnap(s4), IsNil)
  1895  
  1896  	// Connect a few things for the tests below.
  1897  	_, err := r.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "i1"}, SlotRef: SlotRef{Snap: "s2", Name: "i1"}}, nil, nil, nil, nil, nil)
  1898  	c.Assert(err, IsNil)
  1899  	_, err = r.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "i1"}, SlotRef: SlotRef{Snap: "s2", Name: "i1"}}, nil, nil, nil, nil, nil)
  1900  	c.Assert(err, IsNil)
  1901  	_, err = r.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "i2"}, SlotRef: SlotRef{Snap: "s3", Name: "i2"}}, nil, nil, nil, nil, nil)
  1902  	c.Assert(err, IsNil)
  1903  	_, err = r.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s4", Name: "i2"}, SlotRef: SlotRef{Snap: "s3_instance", Name: "i2"}}, nil, nil, nil, nil, nil)
  1904  	c.Assert(err, IsNil)
  1905  
  1906  	// Without any names or options we get the summary of all the interfaces.
  1907  	infos := r.Info(nil)
  1908  	c.Assert(infos, DeepEquals, []*Info{
  1909  		{Name: "i1", Summary: "i1 summary"},
  1910  		{Name: "i2", Summary: "i2 summary"},
  1911  		{Name: "i3", Summary: "i3 summary"},
  1912  	})
  1913  
  1914  	// We can choose specific interfaces, unknown names are just skipped.
  1915  	infos = r.Info(&InfoOptions{Names: []string{"i2", "i4"}})
  1916  	c.Assert(infos, DeepEquals, []*Info{
  1917  		{Name: "i2", Summary: "i2 summary"},
  1918  	})
  1919  
  1920  	// We can ask for documentation.
  1921  	infos = r.Info(&InfoOptions{Names: []string{"i2"}, Doc: true})
  1922  	c.Assert(infos, DeepEquals, []*Info{
  1923  		{Name: "i2", Summary: "i2 summary", DocURL: "http://example.com/i2"},
  1924  	})
  1925  
  1926  	// We can ask for a list of plugs.
  1927  	infos = r.Info(&InfoOptions{Names: []string{"i2"}, Plugs: true})
  1928  	c.Assert(infos, DeepEquals, []*Info{
  1929  		{Name: "i2", Summary: "i2 summary", Plugs: []*snap.PlugInfo{s1.Plugs["i2"], s4.Plugs["i2"]}},
  1930  	})
  1931  
  1932  	// We can ask for a list of slots too.
  1933  	infos = r.Info(&InfoOptions{Names: []string{"i2"}, Slots: true})
  1934  	c.Assert(infos, DeepEquals, []*Info{
  1935  		{Name: "i2", Summary: "i2 summary", Slots: []*snap.SlotInfo{s3.Slots["i2"], s3Instance.Slots["i2"]}},
  1936  	})
  1937  
  1938  	// We can also ask for only those interfaces that have connected plugs or slots.
  1939  	infos = r.Info(&InfoOptions{Connected: true})
  1940  	c.Assert(infos, DeepEquals, []*Info{
  1941  		{Name: "i1", Summary: "i1 summary"},
  1942  		{Name: "i2", Summary: "i2 summary"},
  1943  	})
  1944  }
  1945  
  1946  const ifacehooksSnap1 = `
  1947  name: s1
  1948  version: 0
  1949  plugs:
  1950    consumer:
  1951      interface: iface2
  1952      attr0: val0
  1953  `
  1954  
  1955  const ifacehooksSnap2 = `
  1956  name: s2
  1957  version: 0
  1958  slots:
  1959    producer:
  1960      interface: iface2
  1961      attr0: val0
  1962  `
  1963  
  1964  func (s *RepositorySuite) TestBeforeConnectValidation(c *C) {
  1965  	err := s.emptyRepo.AddInterface(&ifacetest.TestInterface{
  1966  		InterfaceName: "iface2",
  1967  		BeforeConnectSlotCallback: func(slot *ConnectedSlot) error {
  1968  			var val string
  1969  			if err := slot.Attr("attr1", &val); err != nil {
  1970  				return err
  1971  			}
  1972  			return slot.SetAttr("attr1", fmt.Sprintf("%s-validated", val))
  1973  		},
  1974  		BeforeConnectPlugCallback: func(plug *ConnectedPlug) error {
  1975  			var val string
  1976  			if err := plug.Attr("attr1", &val); err != nil {
  1977  				return err
  1978  			}
  1979  			return plug.SetAttr("attr1", fmt.Sprintf("%s-validated", val))
  1980  		},
  1981  	})
  1982  	c.Assert(err, IsNil)
  1983  
  1984  	s1 := snaptest.MockInfo(c, ifacehooksSnap1, nil)
  1985  	c.Assert(s.emptyRepo.AddSnap(s1), IsNil)
  1986  	s2 := snaptest.MockInfo(c, ifacehooksSnap2, nil)
  1987  	c.Assert(s.emptyRepo.AddSnap(s2), IsNil)
  1988  
  1989  	plugDynAttrs := map[string]interface{}{"attr1": "val1"}
  1990  	slotDynAttrs := map[string]interface{}{"attr1": "val1"}
  1991  
  1992  	policyCheck := func(plug *ConnectedPlug, slot *ConnectedSlot) (bool, error) { return true, nil }
  1993  	conn, err := s.emptyRepo.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "consumer"}, SlotRef: SlotRef{Snap: "s2", Name: "producer"}}, nil, plugDynAttrs, nil, slotDynAttrs, policyCheck)
  1994  	c.Assert(err, IsNil)
  1995  	c.Assert(conn, NotNil)
  1996  
  1997  	c.Assert(conn.Plug, NotNil)
  1998  	c.Assert(conn.Slot, NotNil)
  1999  
  2000  	c.Assert(conn.Plug.StaticAttrs(), DeepEquals, map[string]interface{}{"attr0": "val0"})
  2001  	c.Assert(conn.Plug.DynamicAttrs(), DeepEquals, map[string]interface{}{"attr1": "val1-validated"})
  2002  	c.Assert(conn.Slot.StaticAttrs(), DeepEquals, map[string]interface{}{"attr0": "val0"})
  2003  	c.Assert(conn.Slot.DynamicAttrs(), DeepEquals, map[string]interface{}{"attr1": "val1-validated"})
  2004  }
  2005  
  2006  func (s *RepositorySuite) TestBeforeConnectValidationFailure(c *C) {
  2007  	err := s.emptyRepo.AddInterface(&ifacetest.TestInterface{
  2008  		InterfaceName: "iface2",
  2009  		BeforeConnectSlotCallback: func(slot *ConnectedSlot) error {
  2010  			return fmt.Errorf("invalid slot")
  2011  		},
  2012  		BeforeConnectPlugCallback: func(plug *ConnectedPlug) error {
  2013  			return fmt.Errorf("invalid plug")
  2014  		},
  2015  	})
  2016  	c.Assert(err, IsNil)
  2017  
  2018  	s1 := snaptest.MockInfo(c, ifacehooksSnap1, nil)
  2019  	c.Assert(s.emptyRepo.AddSnap(s1), IsNil)
  2020  	s2 := snaptest.MockInfo(c, ifacehooksSnap2, nil)
  2021  	c.Assert(s.emptyRepo.AddSnap(s2), IsNil)
  2022  
  2023  	plugDynAttrs := map[string]interface{}{"attr1": "val1"}
  2024  	slotDynAttrs := map[string]interface{}{"attr1": "val1"}
  2025  
  2026  	policyCheck := func(plug *ConnectedPlug, slot *ConnectedSlot) (bool, error) { return true, nil }
  2027  
  2028  	conn, err := s.emptyRepo.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "consumer"}, SlotRef: SlotRef{Snap: "s2", Name: "producer"}}, nil, plugDynAttrs, nil, slotDynAttrs, policyCheck)
  2029  	c.Assert(err, NotNil)
  2030  	c.Assert(err, ErrorMatches, `cannot connect plug "consumer" of snap "s1": invalid plug`)
  2031  	c.Assert(conn, IsNil)
  2032  }
  2033  
  2034  func (s *RepositorySuite) TestBeforeConnectValidationPolicyCheckFailure(c *C) {
  2035  	err := s.emptyRepo.AddInterface(&ifacetest.TestInterface{
  2036  		InterfaceName:             "iface2",
  2037  		BeforeConnectSlotCallback: func(slot *ConnectedSlot) error { return nil },
  2038  		BeforeConnectPlugCallback: func(plug *ConnectedPlug) error { return nil },
  2039  	})
  2040  	c.Assert(err, IsNil)
  2041  
  2042  	s1 := snaptest.MockInfo(c, ifacehooksSnap1, nil)
  2043  	c.Assert(s.emptyRepo.AddSnap(s1), IsNil)
  2044  	s2 := snaptest.MockInfo(c, ifacehooksSnap2, nil)
  2045  	c.Assert(s.emptyRepo.AddSnap(s2), IsNil)
  2046  
  2047  	plugDynAttrs := map[string]interface{}{"attr1": "val1"}
  2048  	slotDynAttrs := map[string]interface{}{"attr1": "val1"}
  2049  
  2050  	policyCheck := func(plug *ConnectedPlug, slot *ConnectedSlot) (bool, error) {
  2051  		return false, fmt.Errorf("policy check failed")
  2052  	}
  2053  
  2054  	conn, err := s.emptyRepo.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "consumer"}, SlotRef: SlotRef{Snap: "s2", Name: "producer"}}, nil, plugDynAttrs, nil, slotDynAttrs, policyCheck)
  2055  	c.Assert(err, NotNil)
  2056  	c.Assert(err, ErrorMatches, `policy check failed`)
  2057  	c.Assert(conn, IsNil)
  2058  }
  2059  
  2060  func (s *RepositorySuite) TestConnection(c *C) {
  2061  	c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
  2062  	c.Assert(s.testRepo.AddSlot(s.slot), IsNil)
  2063  
  2064  	connRef := NewConnRef(s.plug, s.slot)
  2065  
  2066  	_, err := s.testRepo.Connection(connRef)
  2067  	c.Assert(err, ErrorMatches, `no connection from consumer:plug to producer:slot`)
  2068  
  2069  	_, err = s.testRepo.Connect(connRef, nil, nil, nil, nil, nil)
  2070  	c.Assert(err, IsNil)
  2071  
  2072  	conn, err := s.testRepo.Connection(connRef)
  2073  	c.Assert(err, IsNil)
  2074  	c.Assert(conn.Plug.Name(), Equals, "plug")
  2075  	c.Assert(conn.Slot.Name(), Equals, "slot")
  2076  
  2077  	conn, err = s.testRepo.Connection(&ConnRef{PlugRef: PlugRef{Snap: "a", Name: "b"}, SlotRef: SlotRef{Snap: "producer", Name: "slot"}})
  2078  	c.Assert(err, ErrorMatches, `snap "a" has no plug named "b"`)
  2079  	e, _ := err.(*NoPlugOrSlotError)
  2080  	c.Check(e, NotNil)
  2081  
  2082  	conn, err = s.testRepo.Connection(&ConnRef{PlugRef: PlugRef{Snap: "consumer", Name: "plug"}, SlotRef: SlotRef{Snap: "a", Name: "b"}})
  2083  	c.Assert(err, ErrorMatches, `snap "a" has no slot named "b"`)
  2084  	e, _ = err.(*NoPlugOrSlotError)
  2085  	c.Check(e, NotNil)
  2086  }
  2087  
  2088  func (s *RepositorySuite) TestConnectWithStaticAttrs(c *C) {
  2089  	c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
  2090  	c.Assert(s.testRepo.AddSlot(s.slot), IsNil)
  2091  
  2092  	connRef := NewConnRef(s.plug, s.slot)
  2093  
  2094  	plugAttrs := map[string]interface{}{"foo": "bar"}
  2095  	slotAttrs := map[string]interface{}{"boo": "baz"}
  2096  	_, err := s.testRepo.Connect(connRef, plugAttrs, nil, slotAttrs, nil, nil)
  2097  	c.Assert(err, IsNil)
  2098  
  2099  	conn, err := s.testRepo.Connection(connRef)
  2100  	c.Assert(err, IsNil)
  2101  	c.Assert(conn.Plug.Name(), Equals, "plug")
  2102  	c.Assert(conn.Slot.Name(), Equals, "slot")
  2103  	c.Assert(conn.Plug.StaticAttrs(), DeepEquals, plugAttrs)
  2104  	c.Assert(conn.Slot.StaticAttrs(), DeepEquals, slotAttrs)
  2105  }
  2106  
  2107  func (s *RepositorySuite) TestAllHotplugInterfaces(c *C) {
  2108  	repo := NewRepository()
  2109  	c.Assert(repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "iface1"}), IsNil)
  2110  	c.Assert(repo.AddInterface(&ifacetest.TestHotplugInterface{TestInterface: ifacetest.TestInterface{InterfaceName: "iface2"}}), IsNil)
  2111  	c.Assert(repo.AddInterface(&ifacetest.TestHotplugInterface{TestInterface: ifacetest.TestInterface{InterfaceName: "iface3"}}), IsNil)
  2112  
  2113  	hi := repo.AllHotplugInterfaces()
  2114  	c.Assert(hi, HasLen, 2)
  2115  	c.Assert(hi["iface2"], DeepEquals, &ifacetest.TestHotplugInterface{TestInterface: ifacetest.TestInterface{InterfaceName: "iface2"}})
  2116  	c.Assert(hi["iface3"], DeepEquals, &ifacetest.TestHotplugInterface{TestInterface: ifacetest.TestInterface{InterfaceName: "iface3"}})
  2117  }
  2118  
  2119  func (s *RepositorySuite) TestHotplugMethods(c *C) {
  2120  	c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
  2121  
  2122  	coreSlot := &snap.SlotInfo{
  2123  		Snap:       s.coreSnap,
  2124  		Name:       "dummy-slot",
  2125  		Interface:  "interface",
  2126  		HotplugKey: "1234",
  2127  	}
  2128  	c.Assert(s.testRepo.AddSlot(coreSlot), IsNil)
  2129  
  2130  	slotInfo, err := s.testRepo.SlotForHotplugKey("interface", "1234")
  2131  	c.Assert(err, IsNil)
  2132  	c.Check(slotInfo, DeepEquals, coreSlot)
  2133  
  2134  	// no slot for device key 9999
  2135  	slotInfo, err = s.testRepo.SlotForHotplugKey("interface", "9999")
  2136  	c.Assert(err, IsNil)
  2137  	c.Check(slotInfo, IsNil)
  2138  
  2139  	_, err = s.testRepo.Connect(NewConnRef(s.plug, coreSlot), nil, nil, nil, nil, nil)
  2140  	c.Assert(err, IsNil)
  2141  
  2142  	conns, err := s.testRepo.ConnectionsForHotplugKey("interface", "1234")
  2143  	c.Assert(err, IsNil)
  2144  	c.Check(conns, DeepEquals, []*ConnRef{NewConnRef(s.plug, coreSlot)})
  2145  
  2146  	// no connections for device 9999
  2147  	conns, err = s.testRepo.ConnectionsForHotplugKey("interface", "9999")
  2148  	c.Assert(err, IsNil)
  2149  	c.Check(conns, HasLen, 0)
  2150  }
  2151  
  2152  func (s *RepositorySuite) TestUpdateHotplugSlotAttrs(c *C) {
  2153  	c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
  2154  	coreSlot := &snap.SlotInfo{
  2155  		Snap:       s.coreSnap,
  2156  		Name:       "dummy-slot",
  2157  		Interface:  "interface",
  2158  		HotplugKey: "1234",
  2159  		Attrs:      map[string]interface{}{"a": "b"},
  2160  	}
  2161  	c.Assert(s.testRepo.AddSlot(coreSlot), IsNil)
  2162  
  2163  	slot, err := s.testRepo.UpdateHotplugSlotAttrs("interface", "unknownkey", nil)
  2164  	c.Assert(err, ErrorMatches, `cannot find hotplug slot for interface interface and hotplug key "unknownkey"`)
  2165  	c.Assert(slot, IsNil)
  2166  
  2167  	newAttrs := map[string]interface{}{"c": "d"}
  2168  	slot, err = s.testRepo.UpdateHotplugSlotAttrs("interface", "1234", newAttrs)
  2169  	// attributes are copied, so this change shouldn't be visible
  2170  	newAttrs["c"] = "tainted"
  2171  	c.Assert(err, IsNil)
  2172  	c.Assert(slot, NotNil)
  2173  	c.Assert(slot.Attrs, DeepEquals, map[string]interface{}{"c": "d"})
  2174  	c.Assert(coreSlot.Attrs, DeepEquals, map[string]interface{}{"c": "d"})
  2175  }
  2176  
  2177  func (s *RepositorySuite) TestUpdateHotplugSlotAttrsConnectedError(c *C) {
  2178  	c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
  2179  	coreSlot := &snap.SlotInfo{
  2180  		Snap:       s.coreSnap,
  2181  		Name:       "dummy-slot",
  2182  		Interface:  "interface",
  2183  		HotplugKey: "1234",
  2184  	}
  2185  	c.Assert(s.testRepo.AddSlot(coreSlot), IsNil)
  2186  
  2187  	_, err := s.testRepo.Connect(NewConnRef(s.plug, coreSlot), nil, nil, nil, nil, nil)
  2188  	c.Assert(err, IsNil)
  2189  
  2190  	slot, err := s.testRepo.UpdateHotplugSlotAttrs("interface", "1234", map[string]interface{}{"c": "d"})
  2191  	c.Assert(err, ErrorMatches, `internal error: cannot update slot dummy-slot while connected`)
  2192  	c.Assert(slot, IsNil)
  2193  }