github.com/containerd/Containerd@v1.4.13/metadata/containers_test.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package metadata
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"os"
    24  	"path/filepath"
    25  	"reflect"
    26  	"strings"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/containerd/containerd/containers"
    31  	"github.com/containerd/containerd/errdefs"
    32  	"github.com/containerd/containerd/filters"
    33  	"github.com/containerd/containerd/log/logtest"
    34  	"github.com/containerd/containerd/namespaces"
    35  	"github.com/containerd/typeurl"
    36  	"github.com/gogo/protobuf/types"
    37  	specs "github.com/opencontainers/runtime-spec/specs-go"
    38  	"github.com/pkg/errors"
    39  	bolt "go.etcd.io/bbolt"
    40  )
    41  
    42  func init() {
    43  	typeurl.Register(&specs.Spec{}, "types.containerd.io/opencontainers/runtime-spec", "v1", "Spec")
    44  }
    45  
    46  func TestContainersList(t *testing.T) {
    47  	ctx, db, cancel := testEnv(t)
    48  	defer cancel()
    49  
    50  	store := NewContainerStore(NewDB(db, nil, nil))
    51  
    52  	spec := &specs.Spec{}
    53  	encoded, err := typeurl.MarshalAny(spec)
    54  	if err != nil {
    55  		t.Fatal(err)
    56  	}
    57  
    58  	testset := map[string]*containers.Container{}
    59  	for i := 0; i < 4; i++ {
    60  		id := "container-" + fmt.Sprint(i)
    61  		testset[id] = &containers.Container{
    62  			ID: id,
    63  			Labels: map[string]string{
    64  				"idlabel": id,
    65  				"even":    fmt.Sprint(i%2 == 0),
    66  				"odd":     fmt.Sprint(i%2 != 0),
    67  			},
    68  			Spec:        encoded,
    69  			SnapshotKey: "test-snapshot-key",
    70  			Snapshotter: "snapshotter",
    71  			Runtime: containers.RuntimeInfo{
    72  				Name: "testruntime",
    73  			},
    74  			Image: "test image",
    75  		}
    76  
    77  		if err := db.Update(func(tx *bolt.Tx) error {
    78  			now := time.Now()
    79  			result, err := store.Create(WithTransactionContext(ctx, tx), *testset[id])
    80  			if err != nil {
    81  				return err
    82  			}
    83  
    84  			checkContainerTimestamps(t, &result, now, true)
    85  			testset[id].UpdatedAt, testset[id].CreatedAt = result.UpdatedAt, result.CreatedAt
    86  			checkContainersEqual(t, &result, testset[id], "ensure that containers were created as expected for list")
    87  			return nil
    88  		}); err != nil {
    89  			t.Fatal(err)
    90  		}
    91  	}
    92  
    93  	for _, testcase := range []struct {
    94  		name    string
    95  		filters []string
    96  	}{
    97  		{
    98  			name: "FullSet",
    99  		},
   100  		{
   101  			name:    "FullSetFiltered", // full set, but because we have OR filter
   102  			filters: []string{"labels.even==true", "labels.odd==true"},
   103  		},
   104  		{
   105  			name:    "Even",
   106  			filters: []string{"labels.even==true"},
   107  		},
   108  		{
   109  			name:    "Odd",
   110  			filters: []string{"labels.odd==true"},
   111  		},
   112  		{
   113  			name:    "ByID",
   114  			filters: []string{"id==container-0"},
   115  		},
   116  		{
   117  			name:    "ByIDLabelEven",
   118  			filters: []string{"labels.idlabel==container-0,labels.even==true"},
   119  		},
   120  		{
   121  			name:    "ByRuntime",
   122  			filters: []string{"runtime.name==testruntime"},
   123  		},
   124  	} {
   125  		t.Run(testcase.name, func(t *testing.T) {
   126  			testset := testset
   127  			if len(testcase.filters) > 0 {
   128  				fs, err := filters.ParseAll(testcase.filters...)
   129  				if err != nil {
   130  					t.Fatal(err)
   131  				}
   132  
   133  				newtestset := make(map[string]*containers.Container, len(testset))
   134  				for k, v := range testset {
   135  					if fs.Match(adaptContainer(*v)) {
   136  						newtestset[k] = v
   137  					}
   138  				}
   139  				testset = newtestset
   140  			}
   141  
   142  			results, err := store.List(ctx, testcase.filters...)
   143  			if err != nil {
   144  				t.Fatal(err)
   145  			}
   146  
   147  			if len(results) == 0 { // all tests return a non-empty result set
   148  				t.Fatalf("not results returned")
   149  			}
   150  
   151  			if len(results) != len(testset) {
   152  				t.Fatalf("length of result does not match testset: %v != %v", len(results), len(testset))
   153  			}
   154  
   155  			for _, result := range results {
   156  				checkContainersEqual(t, &result, testset[result.ID], "list results did not match")
   157  			}
   158  		})
   159  	}
   160  
   161  	// delete everything to test it
   162  	for id := range testset {
   163  		if err := store.Delete(ctx, id); err != nil {
   164  			t.Fatal(err)
   165  		}
   166  
   167  		// try it again, get NotFound
   168  		if err := store.Delete(ctx, id); err == nil {
   169  			t.Fatalf("expected error deleting non-existent container")
   170  		} else if !errdefs.IsNotFound(err) {
   171  			t.Fatalf("unexpected error %v", err)
   172  		}
   173  	}
   174  }
   175  
   176  // TestContainersUpdate ensures that updates are taken in an expected manner.
   177  func TestContainersCreateUpdateDelete(t *testing.T) {
   178  	ctx, db, cancel := testEnv(t)
   179  	defer cancel()
   180  
   181  	store := NewContainerStore(NewDB(db, nil, nil))
   182  
   183  	spec := &specs.Spec{}
   184  	encoded, err := typeurl.MarshalAny(spec)
   185  	if err != nil {
   186  		t.Fatal(err)
   187  	}
   188  
   189  	spec.Annotations = map[string]string{"updated": "true"}
   190  	encodedUpdated, err := typeurl.MarshalAny(spec)
   191  	if err != nil {
   192  		t.Fatal(err)
   193  	}
   194  
   195  	for _, testcase := range []struct {
   196  		name       string
   197  		original   containers.Container
   198  		createerr  error
   199  		input      containers.Container
   200  		fieldpaths []string
   201  		expected   containers.Container
   202  		cause      error
   203  	}{
   204  		{
   205  			name: "UpdateIDFail",
   206  			original: containers.Container{
   207  				Spec:        encoded,
   208  				SnapshotKey: "test-snapshot-key",
   209  				Snapshotter: "snapshotter",
   210  				Runtime: containers.RuntimeInfo{
   211  					Name: "testruntime",
   212  				},
   213  			},
   214  			input: containers.Container{
   215  				ID:   "newid",
   216  				Spec: encoded,
   217  				Runtime: containers.RuntimeInfo{
   218  					Name: "testruntime",
   219  				},
   220  			},
   221  			fieldpaths: []string{"id"},
   222  			cause:      errdefs.ErrNotFound,
   223  		},
   224  		{
   225  			name: "UpdateRuntimeFail",
   226  			original: containers.Container{
   227  				SnapshotKey: "test-snapshot-key",
   228  				Snapshotter: "snapshotter",
   229  				Spec:        encoded,
   230  				Runtime: containers.RuntimeInfo{
   231  					Name: "testruntime",
   232  				},
   233  			},
   234  			input: containers.Container{
   235  				Spec: encoded,
   236  				Runtime: containers.RuntimeInfo{
   237  					Name: "testruntimedifferent",
   238  				},
   239  			},
   240  			fieldpaths: []string{"runtime"},
   241  			cause:      errdefs.ErrInvalidArgument,
   242  		},
   243  		{
   244  			name: "UpdateRuntimeClearFail",
   245  			original: containers.Container{
   246  				Spec:        encoded,
   247  				SnapshotKey: "test-snapshot-key",
   248  				Snapshotter: "snapshotter",
   249  				Runtime: containers.RuntimeInfo{
   250  					Name: "testruntime",
   251  				},
   252  			},
   253  			input: containers.Container{
   254  				Spec: encoded,
   255  			},
   256  			fieldpaths: []string{"runtime"},
   257  			cause:      errdefs.ErrInvalidArgument,
   258  		},
   259  		{
   260  			name: "UpdateSpec",
   261  			original: containers.Container{
   262  				Spec:        encoded,
   263  				SnapshotKey: "test-snapshot-key",
   264  				Snapshotter: "snapshotter",
   265  				Runtime: containers.RuntimeInfo{
   266  					Name: "testruntime",
   267  				},
   268  				Image: "test image",
   269  			},
   270  			input: containers.Container{
   271  				Spec: encodedUpdated,
   272  			},
   273  			fieldpaths: []string{"spec"},
   274  			expected: containers.Container{
   275  				Runtime: containers.RuntimeInfo{
   276  					Name: "testruntime",
   277  				},
   278  				Spec:        encodedUpdated,
   279  				SnapshotKey: "test-snapshot-key",
   280  				Snapshotter: "snapshotter",
   281  				Image:       "test image",
   282  			},
   283  		},
   284  		{
   285  			name: "UpdateSnapshot",
   286  			original: containers.Container{
   287  
   288  				Spec:        encoded,
   289  				SnapshotKey: "test-snapshot-key",
   290  				Snapshotter: "snapshotter",
   291  				Runtime: containers.RuntimeInfo{
   292  					Name: "testruntime",
   293  				},
   294  				Image: "test image",
   295  			},
   296  			input: containers.Container{
   297  				SnapshotKey: "test2-snapshot-key",
   298  			},
   299  			fieldpaths: []string{"snapshotkey"},
   300  			expected: containers.Container{
   301  
   302  				Spec:        encoded,
   303  				SnapshotKey: "test2-snapshot-key",
   304  				Snapshotter: "snapshotter",
   305  				Runtime: containers.RuntimeInfo{
   306  					Name: "testruntime",
   307  				},
   308  				Image: "test image",
   309  			},
   310  		},
   311  		{
   312  			name: "UpdateImage",
   313  			original: containers.Container{
   314  
   315  				Spec:        encoded,
   316  				SnapshotKey: "test-snapshot-key",
   317  				Snapshotter: "snapshotter",
   318  				Runtime: containers.RuntimeInfo{
   319  					Name: "testruntime",
   320  				},
   321  				Image: "test image",
   322  			},
   323  			input: containers.Container{
   324  				Image: "test2 image",
   325  			},
   326  			fieldpaths: []string{"image"},
   327  			expected: containers.Container{
   328  
   329  				Spec:        encoded,
   330  				SnapshotKey: "test-snapshot-key",
   331  				Snapshotter: "snapshotter",
   332  				Runtime: containers.RuntimeInfo{
   333  					Name: "testruntime",
   334  				},
   335  				Image: "test2 image",
   336  			},
   337  		},
   338  		{
   339  			name: "UpdateLabel",
   340  			original: containers.Container{
   341  				Labels: map[string]string{
   342  					"foo": "one",
   343  					"bar": "two",
   344  				},
   345  				Spec:        encoded,
   346  				SnapshotKey: "test-snapshot-key",
   347  				Snapshotter: "snapshotter",
   348  				Runtime: containers.RuntimeInfo{
   349  					Name: "testruntime",
   350  				},
   351  				Image: "test image",
   352  			},
   353  			input: containers.Container{
   354  				Labels: map[string]string{
   355  					"bar": "baz",
   356  				},
   357  			},
   358  			fieldpaths: []string{"labels.bar"},
   359  			expected: containers.Container{
   360  				Labels: map[string]string{
   361  					"foo": "one",
   362  					"bar": "baz",
   363  				},
   364  				Spec:        encoded,
   365  				SnapshotKey: "test-snapshot-key",
   366  				Snapshotter: "snapshotter",
   367  				Runtime: containers.RuntimeInfo{
   368  					Name: "testruntime",
   369  				},
   370  				Image: "test image",
   371  			},
   372  		},
   373  		{
   374  			name: "DeleteAllLabels",
   375  			original: containers.Container{
   376  				Labels: map[string]string{
   377  					"foo": "one",
   378  					"bar": "two",
   379  				},
   380  				Spec:        encoded,
   381  				SnapshotKey: "test-snapshot-key",
   382  				Snapshotter: "snapshotter",
   383  				Runtime: containers.RuntimeInfo{
   384  					Name: "testruntime",
   385  				},
   386  				Image: "test image",
   387  			},
   388  			input: containers.Container{
   389  				Labels: nil,
   390  			},
   391  			fieldpaths: []string{"labels"},
   392  			expected: containers.Container{
   393  				Spec:        encoded,
   394  				SnapshotKey: "test-snapshot-key",
   395  				Snapshotter: "snapshotter",
   396  				Runtime: containers.RuntimeInfo{
   397  					Name: "testruntime",
   398  				},
   399  				Image: "test image",
   400  			},
   401  		},
   402  		{
   403  			name: "DeleteLabel",
   404  			original: containers.Container{
   405  				Labels: map[string]string{
   406  					"foo": "one",
   407  					"bar": "two",
   408  				},
   409  				Spec:        encoded,
   410  				SnapshotKey: "test-snapshot-key",
   411  				Snapshotter: "snapshotter",
   412  				Runtime: containers.RuntimeInfo{
   413  					Name: "testruntime",
   414  				},
   415  				Image: "test image",
   416  			},
   417  			input: containers.Container{
   418  				Labels: map[string]string{
   419  					"bar": "",
   420  				},
   421  			},
   422  			fieldpaths: []string{"labels.bar"},
   423  			expected: containers.Container{
   424  				Labels: map[string]string{
   425  					"foo": "one",
   426  				},
   427  				Spec:        encoded,
   428  				SnapshotKey: "test-snapshot-key",
   429  				Snapshotter: "snapshotter",
   430  				Runtime: containers.RuntimeInfo{
   431  					Name: "testruntime",
   432  				},
   433  				Image: "test image",
   434  			},
   435  		},
   436  		{
   437  			name: "UpdateSnapshotKeyImmutable",
   438  			original: containers.Container{
   439  				Spec:        encoded,
   440  				SnapshotKey: "",
   441  				Snapshotter: "",
   442  				Runtime: containers.RuntimeInfo{
   443  					Name: "testruntime",
   444  				},
   445  			},
   446  			input: containers.Container{
   447  				SnapshotKey: "something",
   448  				Snapshotter: "something",
   449  			},
   450  			fieldpaths: []string{"snapshotkey", "snapshotter"},
   451  			cause:      errdefs.ErrInvalidArgument,
   452  		},
   453  		{
   454  			name: "SnapshotKeyWithoutSnapshot",
   455  			original: containers.Container{
   456  				Spec:        encoded,
   457  				SnapshotKey: "/nosnapshot",
   458  				Snapshotter: "",
   459  				Runtime: containers.RuntimeInfo{
   460  					Name: "testruntime",
   461  				},
   462  			},
   463  			createerr: errdefs.ErrInvalidArgument,
   464  		},
   465  		{
   466  			name: "UpdateExtensionsFull",
   467  			original: containers.Container{
   468  				Spec: encoded,
   469  				Runtime: containers.RuntimeInfo{
   470  					Name: "testruntime",
   471  				},
   472  				Extensions: map[string]types.Any{
   473  					"hello": {
   474  						TypeUrl: "test.update.extensions",
   475  						Value:   []byte("hello"),
   476  					},
   477  				},
   478  			},
   479  			input: containers.Container{
   480  				Spec: encoded,
   481  				Runtime: containers.RuntimeInfo{
   482  					Name: "testruntime",
   483  				},
   484  				Extensions: map[string]types.Any{
   485  					"hello": {
   486  						TypeUrl: "test.update.extensions",
   487  						Value:   []byte("world"),
   488  					},
   489  				},
   490  			},
   491  			expected: containers.Container{
   492  				Spec: encoded,
   493  				Runtime: containers.RuntimeInfo{
   494  					Name: "testruntime",
   495  				},
   496  				Extensions: map[string]types.Any{
   497  					"hello": {
   498  						TypeUrl: "test.update.extensions",
   499  						Value:   []byte("world"),
   500  					},
   501  				},
   502  			},
   503  		},
   504  		{
   505  			name: "UpdateExtensionsNotInFieldpath",
   506  			original: containers.Container{
   507  				Spec: encoded,
   508  				Runtime: containers.RuntimeInfo{
   509  					Name: "testruntime",
   510  				},
   511  				Extensions: map[string]types.Any{
   512  					"hello": {
   513  						TypeUrl: "test.update.extensions",
   514  						Value:   []byte("hello"),
   515  					},
   516  				},
   517  			},
   518  			input: containers.Container{
   519  				Spec: encoded,
   520  				Runtime: containers.RuntimeInfo{
   521  					Name: "testruntime",
   522  				},
   523  				Extensions: map[string]types.Any{
   524  					"hello": {
   525  						TypeUrl: "test.update.extensions",
   526  						Value:   []byte("world"),
   527  					},
   528  				},
   529  			},
   530  			fieldpaths: []string{"labels"},
   531  			expected: containers.Container{
   532  				Spec: encoded,
   533  				Runtime: containers.RuntimeInfo{
   534  					Name: "testruntime",
   535  				},
   536  				Extensions: map[string]types.Any{
   537  					"hello": {
   538  						TypeUrl: "test.update.extensions",
   539  						Value:   []byte("hello"),
   540  					},
   541  				},
   542  			},
   543  		},
   544  		{
   545  			name: "UpdateExtensionsFieldPath",
   546  			original: containers.Container{
   547  				Spec: encoded,
   548  				Runtime: containers.RuntimeInfo{
   549  					Name: "testruntime",
   550  				},
   551  				Extensions: map[string]types.Any{
   552  					"hello": {
   553  						TypeUrl: "test.update.extensions",
   554  						Value:   []byte("hello"),
   555  					},
   556  				},
   557  			},
   558  			input: containers.Container{
   559  				Labels: map[string]string{
   560  					"foo": "one",
   561  				},
   562  				Extensions: map[string]types.Any{
   563  					"hello": {
   564  						TypeUrl: "test.update.extensions",
   565  						Value:   []byte("world"),
   566  					},
   567  				},
   568  			},
   569  			fieldpaths: []string{"extensions"},
   570  			expected: containers.Container{
   571  				Spec: encoded,
   572  				Runtime: containers.RuntimeInfo{
   573  					Name: "testruntime",
   574  				},
   575  				Extensions: map[string]types.Any{
   576  					"hello": {
   577  						TypeUrl: "test.update.extensions",
   578  						Value:   []byte("world"),
   579  					},
   580  				},
   581  			},
   582  		},
   583  		{
   584  			name: "UpdateExtensionsFieldPathIsolated",
   585  			original: containers.Container{
   586  				Spec: encoded,
   587  				Runtime: containers.RuntimeInfo{
   588  					Name: "testruntime",
   589  				},
   590  				Extensions: map[string]types.Any{
   591  					// leaves hello in place.
   592  					"hello": {
   593  						TypeUrl: "test.update.extensions",
   594  						Value:   []byte("hello"),
   595  					},
   596  				},
   597  			},
   598  			input: containers.Container{
   599  				Extensions: map[string]types.Any{
   600  					"hello": {
   601  						TypeUrl: "test.update.extensions",
   602  						Value:   []byte("universe"), // this will be ignored
   603  					},
   604  					"bar": {
   605  						TypeUrl: "test.update.extensions",
   606  						Value:   []byte("foo"), // this will be added
   607  					},
   608  				},
   609  			},
   610  			fieldpaths: []string{"extensions.bar"}, //
   611  			expected: containers.Container{
   612  				Spec: encoded,
   613  				Runtime: containers.RuntimeInfo{
   614  					Name: "testruntime",
   615  				},
   616  				Extensions: map[string]types.Any{
   617  					"hello": {
   618  						TypeUrl: "test.update.extensions",
   619  						Value:   []byte("hello"), // remains as world
   620  					},
   621  					"bar": {
   622  						TypeUrl: "test.update.extensions",
   623  						Value:   []byte("foo"), // this will be added
   624  					},
   625  				},
   626  			},
   627  		},
   628  	} {
   629  		t.Run(testcase.name, func(t *testing.T) {
   630  			testcase.original.ID = testcase.name
   631  			if testcase.input.ID == "" {
   632  				testcase.input.ID = testcase.name
   633  			}
   634  			testcase.expected.ID = testcase.name
   635  
   636  			now := time.Now().UTC()
   637  
   638  			result, err := store.Create(ctx, testcase.original)
   639  			if !errors.Is(err, testcase.createerr) {
   640  				if testcase.createerr == nil {
   641  					t.Fatalf("unexpected error: %v", err)
   642  				} else {
   643  					t.Fatalf("cause of %v (cause: %v) != %v", err, errors.Cause(err), testcase.createerr)
   644  				}
   645  			} else if testcase.createerr != nil {
   646  				return
   647  			}
   648  
   649  			checkContainerTimestamps(t, &result, now, true)
   650  
   651  			// ensure that createdat is never tampered with
   652  			testcase.original.CreatedAt = result.CreatedAt
   653  			testcase.expected.CreatedAt = result.CreatedAt
   654  			testcase.original.UpdatedAt = result.UpdatedAt
   655  			testcase.expected.UpdatedAt = result.UpdatedAt
   656  
   657  			checkContainersEqual(t, &result, &testcase.original, "unexpected result on container update")
   658  
   659  			now = time.Now()
   660  			result, err = store.Update(ctx, testcase.input, testcase.fieldpaths...)
   661  			if !errors.Is(err, testcase.cause) {
   662  				if testcase.cause == nil {
   663  					t.Fatalf("unexpected error: %v", err)
   664  				} else {
   665  					t.Fatalf("cause of %v (cause: %v) != %v", err, errors.Cause(err), testcase.cause)
   666  				}
   667  			} else if testcase.cause != nil {
   668  				return
   669  			}
   670  
   671  			checkContainerTimestamps(t, &result, now, false)
   672  			testcase.expected.UpdatedAt = result.UpdatedAt
   673  			checkContainersEqual(t, &result, &testcase.expected, "updated failed to get expected result")
   674  
   675  			result, err = store.Get(ctx, testcase.original.ID)
   676  			if err != nil {
   677  				t.Fatal(err)
   678  			}
   679  
   680  			checkContainersEqual(t, &result, &testcase.expected, "get after failed to get expected result")
   681  		})
   682  	}
   683  }
   684  
   685  func checkContainerTimestamps(t *testing.T, c *containers.Container, now time.Time, oncreate bool) {
   686  	if c.UpdatedAt.IsZero() || c.CreatedAt.IsZero() {
   687  		t.Fatalf("timestamps not set")
   688  	}
   689  
   690  	if oncreate {
   691  		if !c.CreatedAt.Equal(c.UpdatedAt) {
   692  			t.Fatal("timestamps should be equal on create")
   693  		}
   694  
   695  	} else {
   696  		// ensure that updatedat is always after createdat
   697  		if !c.UpdatedAt.After(c.CreatedAt) {
   698  			t.Fatalf("timestamp for updatedat not after createdat: %v <= %v", c.UpdatedAt, c.CreatedAt)
   699  		}
   700  	}
   701  
   702  	if c.UpdatedAt.Before(now) {
   703  		t.Fatal("createdat time incorrect should be after the start of the operation")
   704  	}
   705  }
   706  
   707  func checkContainersEqual(t *testing.T, a, b *containers.Container, format string, args ...interface{}) {
   708  	if !reflect.DeepEqual(a, b) {
   709  		t.Fatalf("containers not equal \n\t%v != \n\t%v: "+format, append([]interface{}{a, b}, args...)...)
   710  	}
   711  }
   712  
   713  func testEnv(t *testing.T) (context.Context, *bolt.DB, func()) {
   714  	ctx, cancel := context.WithCancel(context.Background())
   715  	ctx = namespaces.WithNamespace(ctx, "testing")
   716  	ctx = logtest.WithT(ctx, t)
   717  
   718  	dirname, err := ioutil.TempDir("", strings.Replace(t.Name(), "/", "_", -1)+"-")
   719  	if err != nil {
   720  		t.Fatal(err)
   721  	}
   722  
   723  	db, err := bolt.Open(filepath.Join(dirname, "meta.db"), 0644, nil)
   724  	if err != nil {
   725  		t.Fatal(err)
   726  	}
   727  
   728  	return ctx, db, func() {
   729  		db.Close()
   730  		if err := os.RemoveAll(dirname); err != nil {
   731  			t.Log("failed removing temp dir", err)
   732  		}
   733  		cancel()
   734  	}
   735  }