github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/overlord/configstate/config/transaction_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 config_test
    21  
    22  import (
    23  	"bytes"
    24  	"encoding/json"
    25  	"fmt"
    26  	"strings"
    27  	"testing"
    28  
    29  	. "gopkg.in/check.v1"
    30  
    31  	"github.com/snapcore/snapd/jsonutil"
    32  	"github.com/snapcore/snapd/overlord/configstate/config"
    33  	"github.com/snapcore/snapd/overlord/state"
    34  )
    35  
    36  func TestT(t *testing.T) { TestingT(t) }
    37  
    38  type transactionSuite struct {
    39  	state       *state.State
    40  	transaction *config.Transaction
    41  }
    42  
    43  var _ = Suite(&transactionSuite{})
    44  
    45  func (s *transactionSuite) SetUpTest(c *C) {
    46  	s.state = state.New(nil)
    47  	s.state.Lock()
    48  	defer s.state.Unlock()
    49  	s.transaction = config.NewTransaction(s.state)
    50  }
    51  
    52  type setGetOp string
    53  
    54  func (op setGetOp) kind() string {
    55  	return strings.Fields(string(op))[0]
    56  }
    57  
    58  func (op setGetOp) list() []string {
    59  	args := strings.Fields(string(op))
    60  	return args[1:]
    61  }
    62  
    63  func (op setGetOp) args() map[string]interface{} {
    64  	m := make(map[string]interface{})
    65  	args := strings.Fields(string(op))
    66  	for _, pair := range args[1:] {
    67  		if pair == "=>" {
    68  			break
    69  		}
    70  		kv := strings.SplitN(pair, "=", 2)
    71  		var v interface{}
    72  		if err := jsonutil.DecodeWithNumber(strings.NewReader(kv[1]), &v); err != nil {
    73  			v = kv[1]
    74  		}
    75  		m[kv[0]] = v
    76  	}
    77  	return m
    78  }
    79  
    80  func (op setGetOp) error() string {
    81  	if i := strings.Index(string(op), " => "); i >= 0 {
    82  		return string(op[i+4:])
    83  	}
    84  	return ""
    85  }
    86  
    87  func (op setGetOp) fails() bool {
    88  	return op.error() != ""
    89  }
    90  
    91  var setGetTests = [][]setGetOp{{
    92  	// Basics.
    93  	`get foo=-`,
    94  	`getroot => snap "core" has no configuration`,
    95  	`set one=1 two=2`,
    96  	`set big=1234567890`,
    97  	`setunder three=3 big=9876543210`,
    98  	`get one=1 big=1234567890 two=2 three=-`,
    99  	`getunder one=- two=- three=3 big=9876543210`,
   100  	`changes core.big core.one core.two`,
   101  	`commit`,
   102  	`getunder one=1 two=2 three=3`,
   103  	`get one=1 two=2 three=3`,
   104  	`set two=22 four=4 big=1234567890`,
   105  	`changes core.big core.four core.two`,
   106  	`get one=1 two=22 three=3 four=4 big=1234567890`,
   107  	`getunder one=1 two=2 three=3 four=-`,
   108  	`commit`,
   109  	`getunder one=1 two=22 three=3 four=4`,
   110  }, {
   111  	// Trivial full doc.
   112  	`set doc={"one":1,"two":2}`,
   113  	`get doc={"one":1,"two":2}`,
   114  	`changes core.doc.one core.doc.two`,
   115  }, {
   116  	// Nulls via dotted path
   117  	`set doc={"one":1,"two":2}`,
   118  	`commit`,
   119  	`set doc.one=null`,
   120  	`changes core.doc.one`,
   121  	`get doc={"two":2}`,
   122  	`getunder doc={"one":1,"two":2}`,
   123  	`commit`,
   124  	`get doc={"two":2}`,
   125  	`getroot ={"doc":{"two":2}}`,
   126  	`getunder doc={"two":2}`, // nils are not committed to state
   127  }, {
   128  	// Nulls via dotted path, resuling in empty map
   129  	`set doc={"one":{"three":3},"two":2}`,
   130  	`set doc.one.three=null`,
   131  	`changes core.doc.one.three core.doc.two`,
   132  	`get doc={"one":{},"two":2}`,
   133  	`commit`,
   134  	`get doc={"one":{},"two":2}`,
   135  	`getunder doc={"one":{},"two":2}`, // nils are not committed to state
   136  }, {
   137  	// Nulls via dotted path in a doc
   138  	`set doc={"one":1,"two":2}`,
   139  	`set doc.three={"four":4}`,
   140  	`get doc={"one":1,"two":2,"three":{"four":4}}`,
   141  	`set doc.three={"four":null}`,
   142  	`changes core.doc.one core.doc.three.four core.doc.two`,
   143  	`get doc={"one":1,"two":2,"three":{}}`,
   144  	`commit`,
   145  	`get doc={"one":1,"two":2,"three":{}}`,
   146  	`getunder doc={"one":1,"two":2,"three":{}}`, // nils are not committed to state
   147  }, {
   148  	// Nulls nested in a document
   149  	`set doc={"one":{"three":3,"two":2}}`,
   150  	`changes core.doc.one.three core.doc.one.two`,
   151  	`set doc={"one":{"three":null,"two":2}}`,
   152  	`changes core.doc.one.three core.doc.one.two`,
   153  	`get doc={"one":{"two":2}}`,
   154  	`commit`,
   155  	`get doc={"one":{"two":2}}`,
   156  	`getunder doc={"one":{"two":2}}`, // nils are not committed to state
   157  }, {
   158  	// Nulls with mutating
   159  	`set doc={"one":{"two":2}}`,
   160  	`set doc.one.two=null`,
   161  	`changes core.doc.one.two`,
   162  	`set doc.one="foo"`,
   163  	`get doc.one="foo"`,
   164  	`commit`,
   165  	`get doc={"one":"foo"}`,
   166  	`getunder doc={"one":"foo"}`, // nils are not committed to state
   167  }, {
   168  	// Nulls, intermediate temporary maps
   169  	`set doc={"one":{"two":2}}`,
   170  	`commit`,
   171  	`set doc.one.three.four.five=null`,
   172  	`get doc={"one":{"two":2,"three":{"four":{}}}}`,
   173  	`commit`,
   174  	`get doc={"one":{"two":2,"three":{"four":{}}}}`,
   175  	`getrootunder ={"doc":{"one":{"two":2,"three":{"four":{}}}}}`, // nils are not committed to state
   176  }, {
   177  	// Nulls, same transaction
   178  	`set doc={"one":{"two":2}}`,
   179  	`set doc.one.three.four.five=null`,
   180  	`changes core.doc.one.three.four.five core.doc.one.two`,
   181  	`get doc={"one":{"two":2,"three":{"four":{}}}}`,
   182  	`commit`,
   183  	`get doc={"one":{"two":2,"three":{"four":{}}}}`,
   184  	`getrootunder ={"doc":{"one":{"two":2,"three":{"four":{}}}}}`, // nils are not committed to state
   185  }, {
   186  	// Null leading to empty doc
   187  	`set doc={"one":1}`,
   188  	`set doc.one=null`,
   189  	`changes core.doc.one`,
   190  	`commit`,
   191  	`get doc={}`,
   192  }, {
   193  	// Nulls leading to no snap configuration
   194  	`set doc="foo"`,
   195  	`set doc=null`,
   196  	`changes core.doc`,
   197  	`commit`,
   198  	`get doc=-`,
   199  	`getroot => snap "core" has no configuration`,
   200  }, {
   201  	// set null over non-existing path
   202  	`set x.y.z=null`,
   203  	`changes core.x.y.z`,
   204  	`commit`,
   205  	`get x.y.z=-`,
   206  }, {
   207  	// set null over non-existing path with initial config
   208  	`set foo=bar`,
   209  	`commit`,
   210  	`set x=null`,
   211  	`changes core.x`,
   212  	`commit`,
   213  	`get x=-`,
   214  }, {
   215  	// Nulls, set then unset and set back over same partial path
   216  	`set doc.x.a=1`,
   217  	`commit`,
   218  	`set doc.x.a=null`,
   219  	`get doc={"x":{}}}`,
   220  	`set doc.x.a=6`,
   221  	`get doc={"x":{"a":6}}`,
   222  	`commit`,
   223  	`get doc={"x":{"a":6}}`,
   224  	`getrootunder ={"doc":{"x":{"a":6}}}`,
   225  }, {
   226  	// Nulls, set then unset and set back over same path
   227  	`set doc.x.a=1`,
   228  	`commit`,
   229  	`set doc.x=null`,
   230  	`get doc={}`,
   231  	`set doc.x.a=3`,
   232  	`get doc={"x":{"a":3}}`,
   233  	`commit`,
   234  	`get doc={"x":{"a":3}}`,
   235  	`getrootunder ={"doc":{"x":{"a":3}}}`,
   236  }, {
   237  	// Nulls, set then unset and set back root element
   238  	`set doc.x.a=1`,
   239  	`commit`,
   240  	`set doc.x=null`,
   241  	`get doc={}`,
   242  	`set doc=null`,
   243  	`get doc=-`,
   244  	`set doc=99`,
   245  	`commit`,
   246  	`get doc=99`,
   247  	`getrootunder ={"doc":99}`,
   248  }, {
   249  	// Nulls, set then unset over same path
   250  	`set doc.x.a=1 doc.x.b=2`,
   251  	`commit`,
   252  	`set doc.x=null`,
   253  	`set doc.x.a=null`,
   254  	`set doc.x.b=null`,
   255  	`get doc={"x":{}}`,
   256  	`commit`,
   257  	`get doc={"x":{}}`,
   258  	`getrootunder ={"doc":{"x":{}}}`,
   259  }, {
   260  	// Nulls, set then unset and set back over same path
   261  	`set doc.x.a=1`,
   262  	`commit`,
   263  	`set doc.x=null`,
   264  	`set doc.x.a=null`,
   265  	`get doc={"x":{}}`,
   266  	`set doc={"x":{"a":9}}`,
   267  	`set doc.x.a=1`,
   268  	`get doc={"x":{"a":1}}`,
   269  	`commit`,
   270  	`get doc={"x":{"a":1}}`,
   271  	`getrootunder ={"doc":{"x":{"a":1}}}`,
   272  }, {
   273  	// Root doc
   274  	`set doc={"one":1,"two":2}`,
   275  	`changes core.doc.one core.doc.two`,
   276  	`getroot ={"doc":{"one":1,"two":2}}`,
   277  	`commit`,
   278  	`getroot ={"doc":{"one":1,"two":2}}`,
   279  	`getrootunder ={"doc":{"one":1,"two":2}}`,
   280  }, {
   281  	// Nested mutations.
   282  	`set one.two.three=3`,
   283  	`changes core.one.two.three`,
   284  	`set one.five=5`,
   285  	`changes core.one.five core.one.two.three`,
   286  	`setunder one={"two":{"four":4}}`,
   287  	`get one={"two":{"three":3},"five":5}`,
   288  	`get one.two={"three":3}`,
   289  	`get one.two.three=3`,
   290  	`get one.five=5`,
   291  	`commit`,
   292  	`getunder one={"two":{"three":3,"four":4},"five":5}`,
   293  	`get one={"two":{"three":3,"four":4},"five":5}`,
   294  	`get one.two={"three":3,"four":4}`,
   295  	`get one.two.three=3`,
   296  	`get one.two.four=4`,
   297  	`get one.five=5`,
   298  }, {
   299  	// Nested partial update with full get
   300  	`set one={"two":2,"three":3}`,
   301  	`commit`,
   302  	// update just one subkey
   303  	`set one.two=0`,
   304  	// both subkeys are returned
   305  	`get one={"two":0,"three":3}`,
   306  	`getroot ={"one":{"two":0,"three":3}}`,
   307  	`get one.two=0`,
   308  	`get one.three=3`,
   309  	`getunder one={"two":2,"three":3}`,
   310  	`changes core.one.two`,
   311  	`commit`,
   312  	`getroot ={"one":{"two":0,"three":3}}`,
   313  	`get one={"two":0,"three":3}`,
   314  	`getunder one={"two":0,"three":3}`,
   315  }, {
   316  	// Replacement with nested mutation.
   317  	`set one={"two":{"three":3}}`,
   318  	`changes core.one.two.three`,
   319  	`set one.five=5`,
   320  	`changes core.one.five core.one.two.three`,
   321  	`get one={"two":{"three":3},"five":5}`,
   322  	`get one.two={"three":3}`,
   323  	`get one.two.three=3`,
   324  	`get one.five=5`,
   325  	`setunder one={"two":{"four":4},"six":6}`,
   326  	`commit`,
   327  	`getunder one={"two":{"three":3},"five":5}`,
   328  }, {
   329  	// Cannot go through known scalar implicitly.
   330  	`set one.two=2`,
   331  	`changes core.one.two`,
   332  	`set one.two.three=3 => snap "core" option "one\.two" is not a map`,
   333  	`get one.two.three=3 => snap "core" option "one\.two" is not a map`,
   334  	`get one={"two":2}`,
   335  	`commit`,
   336  	`set one.two.three=3 => snap "core" option "one\.two" is not a map`,
   337  	`get one.two.three=3 => snap "core" option "one\.two" is not a map`,
   338  	`get one={"two":2}`,
   339  	`getunder one={"two":2}`,
   340  }, {
   341  	// Unknown scalars may be overwritten though.
   342  	`setunder one={"two":2}`,
   343  	`set one.two.three=3`,
   344  	`changes core.one.two.three`,
   345  	`commit`,
   346  	`getunder one={"two":{"three":3}}`,
   347  }, {
   348  	// Invalid option names.
   349  	`set BAD=1 => invalid option name: "BAD"`,
   350  	`set 42=1 => invalid option name: "42"`,
   351  	`set .bad=1 => invalid option name: ""`,
   352  	`set bad.=1 => invalid option name: ""`,
   353  	`set bad..bad=1 => invalid option name: ""`,
   354  	`set one.bad--bad.two=1 => invalid option name: "bad--bad"`,
   355  	`set one.-bad.two=1 => invalid option name: "-bad"`,
   356  	`set one.bad-.two=1 => invalid option name: "bad-"`,
   357  }}
   358  
   359  func (s *transactionSuite) TestSetGet(c *C) {
   360  	s.state.Lock()
   361  	defer s.state.Unlock()
   362  
   363  	for _, test := range setGetTests {
   364  		c.Logf("-----")
   365  		s.state.Set("config", map[string]interface{}{})
   366  		t := config.NewTransaction(s.state)
   367  		snap := "core"
   368  		for _, op := range test {
   369  			c.Logf("%s", op)
   370  			switch op.kind() {
   371  			case "set":
   372  				for k, v := range op.args() {
   373  					err := t.Set(snap, k, v)
   374  					if op.fails() {
   375  						c.Assert(err, ErrorMatches, op.error())
   376  					} else {
   377  						c.Assert(err, IsNil)
   378  					}
   379  				}
   380  
   381  			case "get":
   382  				for k, expected := range op.args() {
   383  					var obtained interface{}
   384  					err := t.Get(snap, k, &obtained)
   385  					if op.fails() {
   386  						c.Assert(err, ErrorMatches, op.error())
   387  						var nothing interface{}
   388  						c.Assert(t.GetMaybe(snap, k, &nothing), ErrorMatches, op.error())
   389  						c.Assert(nothing, IsNil)
   390  						continue
   391  					}
   392  					if expected == "-" {
   393  						if !config.IsNoOption(err) {
   394  							c.Fatalf("Expected %q key to not exist, but it has value %v", k, obtained)
   395  						}
   396  						c.Assert(err, ErrorMatches, fmt.Sprintf("snap %q has no %q configuration option", snap, k))
   397  						var nothing interface{}
   398  						c.Assert(t.GetMaybe(snap, k, &nothing), IsNil)
   399  						c.Assert(nothing, IsNil)
   400  						continue
   401  					}
   402  					c.Assert(err, IsNil)
   403  					c.Assert(obtained, DeepEquals, expected)
   404  
   405  					obtained = nil
   406  					c.Assert(t.GetMaybe(snap, k, &obtained), IsNil)
   407  					c.Assert(obtained, DeepEquals, expected)
   408  				}
   409  
   410  			case "commit":
   411  				t.Commit()
   412  
   413  			case "changes":
   414  				expected := op.list()
   415  				obtained := t.Changes()
   416  				c.Check(obtained, DeepEquals, expected)
   417  
   418  			case "setunder":
   419  				var config map[string]map[string]interface{}
   420  				s.state.Get("config", &config)
   421  				if config == nil {
   422  					config = make(map[string]map[string]interface{})
   423  				}
   424  				if config[snap] == nil {
   425  					config[snap] = make(map[string]interface{})
   426  				}
   427  				for k, v := range op.args() {
   428  					if v == "-" {
   429  						delete(config[snap], k)
   430  						if len(config[snap]) == 0 {
   431  							delete(config[snap], snap)
   432  						}
   433  					} else {
   434  						config[snap][k] = v
   435  					}
   436  				}
   437  				s.state.Set("config", config)
   438  
   439  			case "getunder":
   440  				var config map[string]map[string]*json.RawMessage
   441  				s.state.Get("config", &config)
   442  				for k, expected := range op.args() {
   443  					obtained, ok := config[snap][k]
   444  					if expected == "-" {
   445  						if ok {
   446  							c.Fatalf("Expected %q key to not exist, but it has value %v", k, obtained)
   447  						}
   448  						continue
   449  					}
   450  					var cfg interface{}
   451  					c.Assert(jsonutil.DecodeWithNumber(bytes.NewReader(*obtained), &cfg), IsNil)
   452  					c.Assert(cfg, DeepEquals, expected)
   453  				}
   454  			case "getroot":
   455  				var obtained interface{}
   456  				err := t.Get(snap, "", &obtained)
   457  				if op.fails() {
   458  					c.Assert(err, ErrorMatches, op.error())
   459  					continue
   460  				}
   461  				c.Assert(err, IsNil)
   462  				c.Assert(obtained, DeepEquals, op.args()[""])
   463  			case "getrootunder":
   464  				var config map[string]*json.RawMessage
   465  				s.state.Get("config", &config)
   466  				for _, expected := range op.args() {
   467  					obtained, ok := config[snap]
   468  					c.Assert(ok, Equals, true)
   469  					var cfg interface{}
   470  					c.Assert(jsonutil.DecodeWithNumber(bytes.NewReader(*obtained), &cfg), IsNil)
   471  					c.Assert(cfg, DeepEquals, expected)
   472  				}
   473  			default:
   474  				panic("unknown test op kind: " + op.kind())
   475  			}
   476  		}
   477  	}
   478  }
   479  
   480  type brokenType struct {
   481  	on string
   482  }
   483  
   484  func (b *brokenType) UnmarshalJSON(data []byte) error {
   485  	if b.on == string(data) {
   486  		return fmt.Errorf("BAM!")
   487  	}
   488  	return nil
   489  }
   490  
   491  func (s *transactionSuite) TestCommitOverNilSnapConfig(c *C) {
   492  	s.state.Lock()
   493  	defer s.state.Unlock()
   494  
   495  	// simulate invalid nil map created due to LP #1917870 by snap restore
   496  	s.state.Set("config", map[string]interface{}{"test-snap": nil})
   497  	t := config.NewTransaction(s.state)
   498  
   499  	c.Assert(t.Set("test-snap", "foo", "bar"), IsNil)
   500  	t.Commit()
   501  	var v string
   502  	t.Get("test-snap", "foo", &v)
   503  	c.Assert(v, Equals, "bar")
   504  }
   505  
   506  func (s *transactionSuite) TestGetUnmarshalError(c *C) {
   507  	s.state.Lock()
   508  	defer s.state.Unlock()
   509  	c.Check(s.transaction.Set("test-snap", "foo", "good"), IsNil)
   510  	s.transaction.Commit()
   511  
   512  	tr := config.NewTransaction(s.state)
   513  	c.Check(tr.Set("test-snap", "foo", "break"), IsNil)
   514  
   515  	// Pristine state is good, value in the transaction breaks.
   516  	broken := brokenType{`"break"`}
   517  	err := tr.Get("test-snap", "foo", &broken)
   518  	c.Assert(err, ErrorMatches, ".*BAM!.*")
   519  
   520  	// Pristine state breaks, nothing in the transaction.
   521  	tr.Commit()
   522  	err = tr.Get("test-snap", "foo", &broken)
   523  	c.Assert(err, ErrorMatches, ".*BAM!.*")
   524  }
   525  
   526  func (s *transactionSuite) TestNoConfiguration(c *C) {
   527  	s.state.Lock()
   528  	defer s.state.Unlock()
   529  
   530  	var res interface{}
   531  	tr := config.NewTransaction(s.state)
   532  	err := tr.Get("some-snap", "", &res)
   533  	c.Assert(err, NotNil)
   534  	c.Assert(config.IsNoOption(err), Equals, true)
   535  	c.Assert(err, ErrorMatches, `snap "some-snap" has no configuration`)
   536  }
   537  
   538  func (s *transactionSuite) TestState(c *C) {
   539  	s.state.Lock()
   540  	defer s.state.Unlock()
   541  
   542  	tr := config.NewTransaction(s.state)
   543  	c.Check(tr.State(), DeepEquals, s.state)
   544  }
   545  
   546  func (s *transactionSuite) TestPristineIsNotTainted(c *C) {
   547  	s.state.Lock()
   548  	defer s.state.Unlock()
   549  
   550  	tr := config.NewTransaction(s.state)
   551  	c.Check(tr.Set("test-snap", "foo.a.a", "a"), IsNil)
   552  	tr.Commit()
   553  
   554  	var data interface{}
   555  	var result interface{}
   556  	tr = config.NewTransaction(s.state)
   557  	c.Check(tr.Set("test-snap", "foo.b", "b"), IsNil)
   558  	c.Check(tr.Set("test-snap", "foo.a.a", "b"), IsNil)
   559  	c.Assert(tr.Get("test-snap", "foo", &result), IsNil)
   560  	c.Check(result, DeepEquals, map[string]interface{}{"a": map[string]interface{}{"a": "b"}, "b": "b"})
   561  
   562  	pristine := tr.PristineConfig()
   563  	c.Assert(json.Unmarshal([]byte(*pristine["test-snap"]["foo"]), &data), IsNil)
   564  	c.Assert(data, DeepEquals, map[string]interface{}{"a": map[string]interface{}{"a": "a"}})
   565  }
   566  
   567  func (s *transactionSuite) TestPristineGet(c *C) {
   568  	s.state.Lock()
   569  	defer s.state.Unlock()
   570  
   571  	// start with a pristine config
   572  	s.state.Set("config", map[string]map[string]interface{}{
   573  		"some-snap": {"opt1": "pristine-value"},
   574  	})
   575  
   576  	// change the config
   577  	var res interface{}
   578  	tr := config.NewTransaction(s.state)
   579  	err := tr.Set("some-snap", "opt1", "changed-value")
   580  	c.Assert(err, IsNil)
   581  
   582  	// and get will get the changed value
   583  	err = tr.Get("some-snap", "opt1", &res)
   584  	c.Assert(err, IsNil)
   585  	c.Assert(res, Equals, "changed-value")
   586  
   587  	// but GetPristine will get the pristine value
   588  	err = tr.GetPristine("some-snap", "opt1", &res)
   589  	c.Assert(err, IsNil)
   590  	c.Assert(res, Equals, "pristine-value")
   591  
   592  	// and GetPristine errors for options that don't exist in pristine
   593  	var res2 interface{}
   594  	err = tr.Set("some-snap", "opt2", "other-value")
   595  	c.Assert(err, IsNil)
   596  	err = tr.GetPristine("some-snap", "opt2", &res2)
   597  	c.Assert(err, ErrorMatches, `snap "some-snap" has no "opt2" configuration option`)
   598  	// but GetPristineMaybe does not error but also give no value
   599  	err = tr.GetPristineMaybe("some-snap", "opt2", &res2)
   600  	c.Assert(err, IsNil)
   601  	c.Assert(res2, IsNil)
   602  	// but regular get works
   603  	err = tr.Get("some-snap", "opt2", &res2)
   604  	c.Assert(err, IsNil)
   605  	c.Assert(res2, Equals, "other-value")
   606  }