github.com/demonoid81/containerd@v1.3.4/snapshots/storage/metastore_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 storage
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"os"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/containerd/containerd/errdefs"
    28  	"github.com/containerd/containerd/snapshots"
    29  	"github.com/google/go-cmp/cmp"
    30  	"github.com/pkg/errors"
    31  	"gotest.tools/assert"
    32  	is "gotest.tools/assert/cmp"
    33  )
    34  
    35  type testFunc func(context.Context, *testing.T, *MetaStore)
    36  
    37  type metaFactory func(string) (*MetaStore, error)
    38  
    39  type populateFunc func(context.Context, *MetaStore) error
    40  
    41  // MetaStoreSuite runs a test suite on the metastore given a factory function.
    42  func MetaStoreSuite(t *testing.T, name string, meta func(root string) (*MetaStore, error)) {
    43  	t.Run("GetInfo", makeTest(t, name, meta, inReadTransaction(testGetInfo, basePopulate)))
    44  	t.Run("GetInfoNotExist", makeTest(t, name, meta, inReadTransaction(testGetInfoNotExist, basePopulate)))
    45  	t.Run("GetInfoEmptyDB", makeTest(t, name, meta, inReadTransaction(testGetInfoNotExist, nil)))
    46  	t.Run("Walk", makeTest(t, name, meta, inReadTransaction(testWalk, basePopulate)))
    47  	t.Run("GetSnapshot", makeTest(t, name, meta, testGetSnapshot))
    48  	t.Run("GetSnapshotNotExist", makeTest(t, name, meta, inReadTransaction(testGetSnapshotNotExist, basePopulate)))
    49  	t.Run("GetSnapshotCommitted", makeTest(t, name, meta, inReadTransaction(testGetSnapshotCommitted, basePopulate)))
    50  	t.Run("GetSnapshotEmptyDB", makeTest(t, name, meta, inReadTransaction(testGetSnapshotNotExist, basePopulate)))
    51  	t.Run("CreateActive", makeTest(t, name, meta, inWriteTransaction(testCreateActive)))
    52  	t.Run("CreateActiveNotExist", makeTest(t, name, meta, inWriteTransaction(testCreateActiveNotExist)))
    53  	t.Run("CreateActiveExist", makeTest(t, name, meta, inWriteTransaction(testCreateActiveExist)))
    54  	t.Run("CreateActiveFromActive", makeTest(t, name, meta, inWriteTransaction(testCreateActiveFromActive)))
    55  	t.Run("Commit", makeTest(t, name, meta, inWriteTransaction(testCommit)))
    56  	t.Run("CommitNotExist", makeTest(t, name, meta, inWriteTransaction(testCommitExist)))
    57  	t.Run("CommitExist", makeTest(t, name, meta, inWriteTransaction(testCommitExist)))
    58  	t.Run("CommitCommitted", makeTest(t, name, meta, inWriteTransaction(testCommitCommitted)))
    59  	t.Run("CommitViewFails", makeTest(t, name, meta, inWriteTransaction(testCommitViewFails)))
    60  	t.Run("Remove", makeTest(t, name, meta, inWriteTransaction(testRemove)))
    61  	t.Run("RemoveNotExist", makeTest(t, name, meta, inWriteTransaction(testRemoveNotExist)))
    62  	t.Run("RemoveWithChildren", makeTest(t, name, meta, inWriteTransaction(testRemoveWithChildren)))
    63  	t.Run("ParentIDs", makeTest(t, name, meta, inWriteTransaction(testParents)))
    64  }
    65  
    66  // makeTest creates a testsuite with a writable transaction
    67  func makeTest(t *testing.T, name string, metaFn metaFactory, fn testFunc) func(t *testing.T) {
    68  	return func(t *testing.T) {
    69  		ctx := context.Background()
    70  		tmpDir, err := ioutil.TempDir("", "metastore-test-"+name+"-")
    71  		if err != nil {
    72  			t.Fatal(err)
    73  		}
    74  		defer os.RemoveAll(tmpDir)
    75  
    76  		ms, err := metaFn(tmpDir)
    77  		if err != nil {
    78  			t.Fatal(err)
    79  		}
    80  
    81  		fn(ctx, t, ms)
    82  	}
    83  }
    84  
    85  func inReadTransaction(fn testFunc, pf populateFunc) testFunc {
    86  	return func(ctx context.Context, t *testing.T, ms *MetaStore) {
    87  		if pf != nil {
    88  			ctx, tx, err := ms.TransactionContext(ctx, true)
    89  			if err != nil {
    90  				t.Fatal(err)
    91  			}
    92  			if err := pf(ctx, ms); err != nil {
    93  				if rerr := tx.Rollback(); rerr != nil {
    94  					t.Logf("Rollback failed: %+v", rerr)
    95  				}
    96  				t.Fatalf("Populate failed: %+v", err)
    97  			}
    98  			if err := tx.Commit(); err != nil {
    99  				t.Fatalf("Populate commit failed: %+v", err)
   100  			}
   101  		}
   102  
   103  		ctx, tx, err := ms.TransactionContext(ctx, false)
   104  		if err != nil {
   105  			t.Fatalf("Failed start transaction: %+v", err)
   106  		}
   107  		defer func() {
   108  			if err := tx.Rollback(); err != nil {
   109  				t.Logf("Rollback failed: %+v", err)
   110  				if !t.Failed() {
   111  					t.FailNow()
   112  				}
   113  			}
   114  		}()
   115  
   116  		fn(ctx, t, ms)
   117  	}
   118  }
   119  
   120  func inWriteTransaction(fn testFunc) testFunc {
   121  	return func(ctx context.Context, t *testing.T, ms *MetaStore) {
   122  		ctx, tx, err := ms.TransactionContext(ctx, true)
   123  		if err != nil {
   124  			t.Fatalf("Failed to start transaction: %+v", err)
   125  		}
   126  		defer func() {
   127  			if t.Failed() {
   128  				if err := tx.Rollback(); err != nil {
   129  					t.Logf("Rollback failed: %+v", err)
   130  				}
   131  			} else {
   132  				if err := tx.Commit(); err != nil {
   133  					t.Fatalf("Commit failed: %+v", err)
   134  				}
   135  			}
   136  		}()
   137  		fn(ctx, t, ms)
   138  	}
   139  }
   140  
   141  // basePopulate creates 7 snapshots
   142  // - "committed-1": committed without parent
   143  // - "committed-2":  committed with parent "committed-1"
   144  // - "active-1": active without parent
   145  // - "active-2": active with parent "committed-1"
   146  // - "active-3": active with parent "committed-2"
   147  // - "active-4": readonly active without parent"
   148  // - "active-5": readonly active with parent "committed-2"
   149  func basePopulate(ctx context.Context, ms *MetaStore) error {
   150  	if _, err := CreateSnapshot(ctx, snapshots.KindActive, "committed-tmp-1", ""); err != nil {
   151  		return errors.Wrap(err, "failed to create active")
   152  	}
   153  	if _, err := CommitActive(ctx, "committed-tmp-1", "committed-1", snapshots.Usage{Size: 1}); err != nil {
   154  		return errors.Wrap(err, "failed to create active")
   155  	}
   156  	if _, err := CreateSnapshot(ctx, snapshots.KindActive, "committed-tmp-2", "committed-1"); err != nil {
   157  		return errors.Wrap(err, "failed to create active")
   158  	}
   159  	if _, err := CommitActive(ctx, "committed-tmp-2", "committed-2", snapshots.Usage{Size: 2}); err != nil {
   160  		return errors.Wrap(err, "failed to create active")
   161  	}
   162  	if _, err := CreateSnapshot(ctx, snapshots.KindActive, "active-1", ""); err != nil {
   163  		return errors.Wrap(err, "failed to create active")
   164  	}
   165  	if _, err := CreateSnapshot(ctx, snapshots.KindActive, "active-2", "committed-1"); err != nil {
   166  		return errors.Wrap(err, "failed to create active")
   167  	}
   168  	if _, err := CreateSnapshot(ctx, snapshots.KindActive, "active-3", "committed-2"); err != nil {
   169  		return errors.Wrap(err, "failed to create active")
   170  	}
   171  	if _, err := CreateSnapshot(ctx, snapshots.KindView, "view-1", ""); err != nil {
   172  		return errors.Wrap(err, "failed to create active")
   173  	}
   174  	if _, err := CreateSnapshot(ctx, snapshots.KindView, "view-2", "committed-2"); err != nil {
   175  		return errors.Wrap(err, "failed to create active")
   176  	}
   177  	return nil
   178  }
   179  
   180  var baseInfo = map[string]snapshots.Info{
   181  	"committed-1": {
   182  		Name:   "committed-1",
   183  		Parent: "",
   184  		Kind:   snapshots.KindCommitted,
   185  	},
   186  	"committed-2": {
   187  		Name:   "committed-2",
   188  		Parent: "committed-1",
   189  		Kind:   snapshots.KindCommitted,
   190  	},
   191  	"active-1": {
   192  		Name:   "active-1",
   193  		Parent: "",
   194  		Kind:   snapshots.KindActive,
   195  	},
   196  	"active-2": {
   197  		Name:   "active-2",
   198  		Parent: "committed-1",
   199  		Kind:   snapshots.KindActive,
   200  	},
   201  	"active-3": {
   202  		Name:   "active-3",
   203  		Parent: "committed-2",
   204  		Kind:   snapshots.KindActive,
   205  	},
   206  	"view-1": {
   207  		Name:   "view-1",
   208  		Parent: "",
   209  		Kind:   snapshots.KindView,
   210  	},
   211  	"view-2": {
   212  		Name:   "view-2",
   213  		Parent: "committed-2",
   214  		Kind:   snapshots.KindView,
   215  	},
   216  }
   217  
   218  func assertNotExist(t *testing.T, err error) {
   219  	t.Helper()
   220  	assert.Assert(t, errdefs.IsNotFound(err), "got %+v", err)
   221  }
   222  
   223  func assertNotActive(t *testing.T, err error) {
   224  	t.Helper()
   225  	assert.Assert(t, errdefs.IsFailedPrecondition(err), "got %+v", err)
   226  }
   227  
   228  func assertNotCommitted(t *testing.T, err error) {
   229  	t.Helper()
   230  	assert.Assert(t, errdefs.IsInvalidArgument(err), "got %+v", err)
   231  }
   232  
   233  func assertExist(t *testing.T, err error) {
   234  	t.Helper()
   235  	assert.Assert(t, errdefs.IsAlreadyExists(err), "got %+v", err)
   236  }
   237  
   238  func testGetInfo(ctx context.Context, t *testing.T, _ *MetaStore) {
   239  	for key, expected := range baseInfo {
   240  		_, info, _, err := GetInfo(ctx, key)
   241  		assert.NilError(t, err, "on key %v", key)
   242  		assert.Check(t, is.DeepEqual(expected, info, cmpSnapshotInfo), "on key %v", key)
   243  	}
   244  }
   245  
   246  // compare snapshot.Info Updated and Created fields by checking they are
   247  // within a threshold of time.Now()
   248  var cmpSnapshotInfo = cmp.FilterPath(
   249  	func(path cmp.Path) bool {
   250  		field := path.Last().String()
   251  		return field == ".Created" || field == ".Updated"
   252  	},
   253  	cmp.Comparer(func(expected, actual time.Time) bool {
   254  		// cmp.Options must be symmetric, so swap the args
   255  		if actual.IsZero() {
   256  			actual, expected = expected, actual
   257  		}
   258  		if !expected.IsZero() {
   259  			return false
   260  		}
   261  		// actual value should be within a few seconds of now
   262  		now := time.Now()
   263  		delta := now.Sub(actual)
   264  		threshold := 10 * time.Second
   265  		return delta > -threshold && delta < threshold
   266  	}))
   267  
   268  func testGetInfoNotExist(ctx context.Context, t *testing.T, _ *MetaStore) {
   269  	_, _, _, err := GetInfo(ctx, "active-not-exist")
   270  	assertNotExist(t, err)
   271  }
   272  
   273  func testWalk(ctx context.Context, t *testing.T, _ *MetaStore) {
   274  	found := map[string]snapshots.Info{}
   275  	err := WalkInfo(ctx, func(ctx context.Context, info snapshots.Info) error {
   276  		if _, ok := found[info.Name]; ok {
   277  			return errors.Errorf("entry already encountered")
   278  		}
   279  		found[info.Name] = info
   280  		return nil
   281  	})
   282  	assert.NilError(t, err)
   283  	assert.Assert(t, is.DeepEqual(baseInfo, found, cmpSnapshotInfo))
   284  }
   285  
   286  func testGetSnapshot(ctx context.Context, t *testing.T, ms *MetaStore) {
   287  	snapshotMap := map[string]Snapshot{}
   288  	populate := func(ctx context.Context, ms *MetaStore) error {
   289  		if _, err := CreateSnapshot(ctx, snapshots.KindActive, "committed-tmp-1", ""); err != nil {
   290  			return errors.Wrap(err, "failed to create active")
   291  		}
   292  		if _, err := CommitActive(ctx, "committed-tmp-1", "committed-1", snapshots.Usage{}); err != nil {
   293  			return errors.Wrap(err, "failed to create active")
   294  		}
   295  
   296  		for _, opts := range []struct {
   297  			Kind   snapshots.Kind
   298  			Name   string
   299  			Parent string
   300  		}{
   301  			{
   302  				Name: "active-1",
   303  				Kind: snapshots.KindActive,
   304  			},
   305  			{
   306  				Name:   "active-2",
   307  				Parent: "committed-1",
   308  				Kind:   snapshots.KindActive,
   309  			},
   310  			{
   311  				Name: "view-1",
   312  				Kind: snapshots.KindView,
   313  			},
   314  			{
   315  				Name:   "view-2",
   316  				Parent: "committed-1",
   317  				Kind:   snapshots.KindView,
   318  			},
   319  		} {
   320  			active, err := CreateSnapshot(ctx, opts.Kind, opts.Name, opts.Parent)
   321  			if err != nil {
   322  				return errors.Wrap(err, "failed to create active")
   323  			}
   324  			snapshotMap[opts.Name] = active
   325  		}
   326  		return nil
   327  	}
   328  
   329  	test := func(ctx context.Context, t *testing.T, ms *MetaStore) {
   330  		for key, expected := range snapshotMap {
   331  			s, err := GetSnapshot(ctx, key)
   332  			assert.NilError(t, err, "failed to get snapshot %s", key)
   333  			assert.Check(t, is.DeepEqual(expected, s), "on key %s", key)
   334  		}
   335  	}
   336  
   337  	inReadTransaction(test, populate)(ctx, t, ms)
   338  }
   339  
   340  func testGetSnapshotCommitted(ctx context.Context, t *testing.T, ms *MetaStore) {
   341  	_, err := GetSnapshot(ctx, "committed-1")
   342  	assertNotActive(t, err)
   343  }
   344  
   345  func testGetSnapshotNotExist(ctx context.Context, t *testing.T, ms *MetaStore) {
   346  	_, err := GetSnapshot(ctx, "active-not-exist")
   347  	assertNotExist(t, err)
   348  }
   349  
   350  func testCreateActive(ctx context.Context, t *testing.T, ms *MetaStore) {
   351  	a1, err := CreateSnapshot(ctx, snapshots.KindActive, "active-1", "")
   352  	if err != nil {
   353  		t.Fatal(err)
   354  	}
   355  	if a1.Kind != snapshots.KindActive {
   356  		t.Fatal("Expected writable active")
   357  	}
   358  
   359  	a2, err := CreateSnapshot(ctx, snapshots.KindView, "view-1", "")
   360  	if err != nil {
   361  		t.Fatal(err)
   362  	}
   363  	if a2.ID == a1.ID {
   364  		t.Fatal("Returned active identifiers must be unique")
   365  	}
   366  	if a2.Kind != snapshots.KindView {
   367  		t.Fatal("Expected a view")
   368  	}
   369  
   370  	commitID, err := CommitActive(ctx, "active-1", "committed-1", snapshots.Usage{})
   371  	if err != nil {
   372  		t.Fatal(err)
   373  	}
   374  	if commitID != a1.ID {
   375  		t.Fatal("Snapshot identifier must not change on commit")
   376  	}
   377  
   378  	a3, err := CreateSnapshot(ctx, snapshots.KindActive, "active-3", "committed-1")
   379  	if err != nil {
   380  		t.Fatal(err)
   381  	}
   382  	if a3.ID == a1.ID {
   383  		t.Fatal("Returned active identifiers must be unique")
   384  	}
   385  	if len(a3.ParentIDs) != 1 {
   386  		t.Fatalf("Expected 1 parent, got %d", len(a3.ParentIDs))
   387  	}
   388  	if a3.ParentIDs[0] != commitID {
   389  		t.Fatal("Expected active parent to be same as commit ID")
   390  	}
   391  	if a3.Kind != snapshots.KindActive {
   392  		t.Fatal("Expected writable active")
   393  	}
   394  
   395  	a4, err := CreateSnapshot(ctx, snapshots.KindView, "view-2", "committed-1")
   396  	if err != nil {
   397  		t.Fatal(err)
   398  	}
   399  	if a4.ID == a1.ID {
   400  		t.Fatal("Returned active identifiers must be unique")
   401  	}
   402  	if len(a3.ParentIDs) != 1 {
   403  		t.Fatalf("Expected 1 parent, got %d", len(a3.ParentIDs))
   404  	}
   405  	if a3.ParentIDs[0] != commitID {
   406  		t.Fatal("Expected active parent to be same as commit ID")
   407  	}
   408  	if a4.Kind != snapshots.KindView {
   409  		t.Fatal("Expected a view")
   410  	}
   411  }
   412  
   413  func testCreateActiveExist(ctx context.Context, t *testing.T, ms *MetaStore) {
   414  	if err := basePopulate(ctx, ms); err != nil {
   415  		t.Fatalf("Populate failed: %+v", err)
   416  	}
   417  	_, err := CreateSnapshot(ctx, snapshots.KindActive, "active-1", "")
   418  	assertExist(t, err)
   419  	_, err = CreateSnapshot(ctx, snapshots.KindActive, "committed-1", "")
   420  	assertExist(t, err)
   421  }
   422  
   423  func testCreateActiveNotExist(ctx context.Context, t *testing.T, ms *MetaStore) {
   424  	_, err := CreateSnapshot(ctx, snapshots.KindActive, "active-1", "does-not-exist")
   425  	assertNotExist(t, err)
   426  }
   427  
   428  func testCreateActiveFromActive(ctx context.Context, t *testing.T, ms *MetaStore) {
   429  	if err := basePopulate(ctx, ms); err != nil {
   430  		t.Fatalf("Populate failed: %+v", err)
   431  	}
   432  	_, err := CreateSnapshot(ctx, snapshots.KindActive, "active-new", "active-1")
   433  	assertNotCommitted(t, err)
   434  }
   435  
   436  func testCommit(ctx context.Context, t *testing.T, ms *MetaStore) {
   437  	a1, err := CreateSnapshot(ctx, snapshots.KindActive, "active-1", "")
   438  	if err != nil {
   439  		t.Fatal(err)
   440  	}
   441  	if a1.Kind != snapshots.KindActive {
   442  		t.Fatal("Expected writable active")
   443  	}
   444  
   445  	commitID, err := CommitActive(ctx, "active-1", "committed-1", snapshots.Usage{})
   446  	if err != nil {
   447  		t.Fatal(err)
   448  	}
   449  	if commitID != a1.ID {
   450  		t.Fatal("Snapshot identifier must not change on commit")
   451  	}
   452  
   453  	_, err = GetSnapshot(ctx, "active-1")
   454  	assertNotExist(t, err)
   455  	_, err = GetSnapshot(ctx, "committed-1")
   456  	assertNotActive(t, err)
   457  }
   458  
   459  func testCommitExist(ctx context.Context, t *testing.T, ms *MetaStore) {
   460  	if err := basePopulate(ctx, ms); err != nil {
   461  		t.Fatalf("Populate failed: %+v", err)
   462  	}
   463  	_, err := CommitActive(ctx, "active-1", "committed-1", snapshots.Usage{})
   464  	assertExist(t, err)
   465  }
   466  
   467  func testCommitCommitted(ctx context.Context, t *testing.T, ms *MetaStore) {
   468  	if err := basePopulate(ctx, ms); err != nil {
   469  		t.Fatalf("Populate failed: %+v", err)
   470  	}
   471  	_, err := CommitActive(ctx, "committed-1", "committed-3", snapshots.Usage{})
   472  	assertNotActive(t, err)
   473  }
   474  
   475  func testCommitViewFails(ctx context.Context, t *testing.T, ms *MetaStore) {
   476  	if err := basePopulate(ctx, ms); err != nil {
   477  		t.Fatalf("Populate failed: %+v", err)
   478  	}
   479  	_, err := CommitActive(ctx, "view-1", "committed-3", snapshots.Usage{})
   480  	if err == nil {
   481  		t.Fatal("Expected error committing readonly active")
   482  	}
   483  }
   484  
   485  func testRemove(ctx context.Context, t *testing.T, ms *MetaStore) {
   486  	a1, err := CreateSnapshot(ctx, snapshots.KindActive, "active-1", "")
   487  	if err != nil {
   488  		t.Fatal(err)
   489  	}
   490  
   491  	commitID, err := CommitActive(ctx, "active-1", "committed-1", snapshots.Usage{})
   492  	if err != nil {
   493  		t.Fatal(err)
   494  	}
   495  	if commitID != a1.ID {
   496  		t.Fatal("Snapshot identifier must not change on commit")
   497  	}
   498  
   499  	a2, err := CreateSnapshot(ctx, snapshots.KindView, "view-1", "committed-1")
   500  	if err != nil {
   501  		t.Fatal(err)
   502  	}
   503  
   504  	a3, err := CreateSnapshot(ctx, snapshots.KindView, "view-2", "committed-1")
   505  	if err != nil {
   506  		t.Fatal(err)
   507  	}
   508  
   509  	_, _, err = Remove(ctx, "active-1")
   510  	assertNotExist(t, err)
   511  
   512  	r3, k3, err := Remove(ctx, "view-2")
   513  	if err != nil {
   514  		t.Fatal(err)
   515  	}
   516  	if r3 != a3.ID {
   517  		t.Fatal("Expected remove ID to match create ID")
   518  	}
   519  	if k3 != snapshots.KindView {
   520  		t.Fatalf("Expected view kind, got %v", k3)
   521  	}
   522  
   523  	r2, k2, err := Remove(ctx, "view-1")
   524  	if err != nil {
   525  		t.Fatal(err)
   526  	}
   527  	if r2 != a2.ID {
   528  		t.Fatal("Expected remove ID to match create ID")
   529  	}
   530  	if k2 != snapshots.KindView {
   531  		t.Fatalf("Expected view kind, got %v", k2)
   532  	}
   533  
   534  	r1, k1, err := Remove(ctx, "committed-1")
   535  	if err != nil {
   536  		t.Fatal(err)
   537  	}
   538  	if r1 != commitID {
   539  		t.Fatal("Expected remove ID to match commit ID")
   540  	}
   541  	if k1 != snapshots.KindCommitted {
   542  		t.Fatalf("Expected committed kind, got %v", k1)
   543  	}
   544  }
   545  
   546  func testRemoveWithChildren(ctx context.Context, t *testing.T, ms *MetaStore) {
   547  	if err := basePopulate(ctx, ms); err != nil {
   548  		t.Fatalf("Populate failed: %+v", err)
   549  	}
   550  	_, _, err := Remove(ctx, "committed-1")
   551  	if err == nil {
   552  		t.Fatalf("Expected removal of snapshot with children to error")
   553  	}
   554  	_, _, err = Remove(ctx, "committed-1")
   555  	if err == nil {
   556  		t.Fatalf("Expected removal of snapshot with children to error")
   557  	}
   558  }
   559  
   560  func testRemoveNotExist(ctx context.Context, t *testing.T, _ *MetaStore) {
   561  	_, _, err := Remove(ctx, "does-not-exist")
   562  	assertNotExist(t, err)
   563  }
   564  
   565  func testParents(ctx context.Context, t *testing.T, ms *MetaStore) {
   566  	if err := basePopulate(ctx, ms); err != nil {
   567  		t.Fatalf("Populate failed: %+v", err)
   568  	}
   569  
   570  	testcases := []struct {
   571  		Name    string
   572  		Parents int
   573  	}{
   574  		{"committed-1", 0},
   575  		{"committed-2", 1},
   576  		{"active-1", 0},
   577  		{"active-2", 1},
   578  		{"active-3", 2},
   579  		{"view-1", 0},
   580  		{"view-2", 2},
   581  	}
   582  
   583  	for _, tc := range testcases {
   584  		name := tc.Name
   585  		expectedID := ""
   586  		expectedParents := []string{}
   587  		for i := tc.Parents; i >= 0; i-- {
   588  			sid, info, _, err := GetInfo(ctx, name)
   589  			if err != nil {
   590  				t.Fatalf("Failed to get snapshot %s: %v", tc.Name, err)
   591  			}
   592  			var (
   593  				id      string
   594  				parents []string
   595  			)
   596  			if info.Kind == snapshots.KindCommitted {
   597  				// When committed, create view and resolve from view
   598  				nid := fmt.Sprintf("test-%s-%d", tc.Name, i)
   599  				s, err := CreateSnapshot(ctx, snapshots.KindView, nid, name)
   600  				if err != nil {
   601  					t.Fatalf("Failed to get snapshot %s: %v", tc.Name, err)
   602  				}
   603  				if len(s.ParentIDs) != i+1 {
   604  					t.Fatalf("Unexpected number of parents for view of %s: %d, expected %d", name, len(s.ParentIDs), i+1)
   605  				}
   606  				id = s.ParentIDs[0]
   607  				parents = s.ParentIDs[1:]
   608  			} else {
   609  				s, err := GetSnapshot(ctx, name)
   610  				if err != nil {
   611  					t.Fatalf("Failed to get snapshot %s: %v", tc.Name, err)
   612  				}
   613  				if len(s.ParentIDs) != i {
   614  					t.Fatalf("Unexpected number of parents for %s: %d, expected %d", name, len(s.ParentIDs), i)
   615  				}
   616  
   617  				id = s.ID
   618  				parents = s.ParentIDs
   619  			}
   620  			if sid != id {
   621  				t.Fatalf("Info ID mismatched resolved snapshot ID for %s, %s vs %s", name, sid, id)
   622  			}
   623  
   624  			if expectedID != "" {
   625  				if id != expectedID {
   626  					t.Errorf("Unexpected ID of parent: %s, expected %s", id, expectedID)
   627  				}
   628  			}
   629  
   630  			if len(expectedParents) > 0 {
   631  				for j := range expectedParents {
   632  					if parents[j] != expectedParents[j] {
   633  						t.Errorf("Unexpected ID in parent array at %d: %s, expected %s", j, parents[j], expectedParents[j])
   634  					}
   635  				}
   636  			}
   637  
   638  			if i > 0 {
   639  				name = info.Parent
   640  				expectedID = parents[0]
   641  				expectedParents = parents[1:]
   642  			}
   643  
   644  		}
   645  	}
   646  }