github.com/SaurabhDubey-Groww/go-cloud@v0.0.0-20221124105541-b26c29285fd8/runtimevar/drivertest/drivertest.go (about)

     1  // Copyright 2018 The Go Cloud Development Kit Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package drivertest provides a conformance test for implementations of
    16  // runtimevar.
    17  package drivertest // import "gocloud.dev/runtimevar/drivertest"
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"strings"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/google/go-cmp/cmp"
    27  	"gocloud.dev/gcerrors"
    28  	"gocloud.dev/internal/testing/setup"
    29  	"gocloud.dev/runtimevar"
    30  	"gocloud.dev/runtimevar/driver"
    31  )
    32  
    33  // Harness describes the functionality test harnesses must provide to run conformance tests.
    34  type Harness interface {
    35  	// MakeWatcher creates a driver.Watcher to watch the given variable.
    36  	MakeWatcher(ctx context.Context, name string, decoder *runtimevar.Decoder) (driver.Watcher, error)
    37  	// CreateVariable creates the variable with the given contents.
    38  	CreateVariable(ctx context.Context, name string, val []byte) error
    39  	// UpdateVariable updates an existing variable to have the given contents.
    40  	UpdateVariable(ctx context.Context, name string, val []byte) error
    41  	// DeleteVariable deletes an existing variable.
    42  	DeleteVariable(ctx context.Context, name string) error
    43  	// Close is called when the test is complete.
    44  	Close()
    45  	// Mutable returns true iff the driver supports UpdateVariable/DeleteVariable.
    46  	// If false, those functions should return errors, and the conformance tests
    47  	// will skip and/or ignore errors for tests that require them.
    48  	Mutable() bool
    49  }
    50  
    51  // HarnessMaker describes functions that construct a harness for running tests.
    52  // It is called exactly once per test; Harness.Close() will be called when the test is complete.
    53  type HarnessMaker func(t *testing.T) (Harness, error)
    54  
    55  // AsTest represents a test of As functionality.
    56  // The conformance test:
    57  // 1. Reads a Snapshot of the variable before it exists.
    58  // 2. Calls ErrorCheck.
    59  // 3. Creates the variable and reads a Snapshot of it.
    60  // 4. Calls SnapshotCheck.
    61  type AsTest interface {
    62  	// Name should return a descriptive name for the test.
    63  	Name() string
    64  	// SnapshotCheck will be called to allow verification of Snapshot.As.
    65  	SnapshotCheck(s *runtimevar.Snapshot) error
    66  	// ErrorCheck will be called to allow verification of Variable.ErrorAs.
    67  	// driver is provided so that errors other than err can be checked;
    68  	// Variable.ErrorAs won't work since it expects driver errors to be wrapped.
    69  	ErrorCheck(v *runtimevar.Variable, err error) error
    70  }
    71  
    72  type verifyAsFailsOnNil struct{}
    73  
    74  func (verifyAsFailsOnNil) Name() string {
    75  	return "verify As returns false when passed nil"
    76  }
    77  
    78  func (verifyAsFailsOnNil) SnapshotCheck(v *runtimevar.Snapshot) error {
    79  	if v.As(nil) {
    80  		return errors.New("want Snapshot.As to return false when passed nil")
    81  	}
    82  	return nil
    83  }
    84  
    85  func (verifyAsFailsOnNil) ErrorCheck(v *runtimevar.Variable, err error) (ret error) {
    86  	defer func() {
    87  		if recover() == nil {
    88  			ret = errors.New("want ErrorAs to panic when passed nil")
    89  		}
    90  	}()
    91  	v.ErrorAs(err, nil)
    92  	return nil
    93  }
    94  
    95  // RunConformanceTests runs conformance tests for driver implementations
    96  // of runtimevar.
    97  func RunConformanceTests(t *testing.T, newHarness HarnessMaker, asTests []AsTest) {
    98  	t.Run("TestNonExistentVariable", func(t *testing.T) {
    99  		testNonExistentVariable(t, newHarness)
   100  	})
   101  	t.Run("TestString", func(t *testing.T) {
   102  		testString(t, newHarness)
   103  	})
   104  	t.Run("TestJSON", func(t *testing.T) {
   105  		testJSON(t, newHarness)
   106  	})
   107  	t.Run("TestInvalidJSON", func(t *testing.T) {
   108  		testInvalidJSON(t, newHarness)
   109  	})
   110  	t.Run("TestUpdate", func(t *testing.T) {
   111  		testUpdate(t, newHarness)
   112  	})
   113  	t.Run("TestDelete", func(t *testing.T) {
   114  		testDelete(t, newHarness)
   115  	})
   116  	t.Run("TestUpdateWithErrors", func(t *testing.T) {
   117  		testUpdateWithErrors(t, newHarness)
   118  	})
   119  	asTests = append(asTests, verifyAsFailsOnNil{})
   120  	t.Run("TestAs", func(t *testing.T) {
   121  		for _, st := range asTests {
   122  			if st.Name() == "" {
   123  				t.Fatalf("AsTest.Name is required")
   124  			}
   125  			t.Run(st.Name(), func(t *testing.T) {
   126  				testAs(t, newHarness, st)
   127  			})
   128  		}
   129  	})
   130  }
   131  
   132  // deadlineExceeded returns true if err represents a context exceeded error.
   133  // It can either be a true context.DeadlineExceeded, or an RPC aborted due to
   134  // ctx cancellation; we don't have a good way of checking for the latter
   135  // explicitly so we check the Error() string.
   136  func deadlineExceeded(err error) bool {
   137  	return err == context.DeadlineExceeded || strings.Contains(err.Error(), "context deadline exceeded")
   138  }
   139  
   140  // waitTimeForBlockingCheck returns a duration to wait when verifying that a
   141  // call blocks. When in replay mode, it can be quite short to make tests run
   142  // quickly. When in record mode, it has to be long enough that RPCs can
   143  // consistently finish.
   144  func waitTimeForBlockingCheck() time.Duration {
   145  	if *setup.Record {
   146  		return 5 * time.Second
   147  	}
   148  	return 10 * time.Millisecond
   149  }
   150  
   151  func testNonExistentVariable(t *testing.T, newHarness HarnessMaker) {
   152  	h, err := newHarness(t)
   153  	if err != nil {
   154  		t.Fatal(err)
   155  	}
   156  	defer h.Close()
   157  	ctx := context.Background()
   158  
   159  	drv, err := h.MakeWatcher(ctx, "does-not-exist", runtimevar.StringDecoder)
   160  	if err != nil {
   161  		t.Fatal(err)
   162  	}
   163  	v := runtimevar.New(drv)
   164  	defer func() {
   165  		if err := v.Close(); err != nil {
   166  			t.Error(err)
   167  		}
   168  	}()
   169  	got, err := v.Watch(ctx)
   170  	if err == nil {
   171  		t.Errorf("got %v expected not-found error", got.Value)
   172  	} else if gcerrors.Code(err) != gcerrors.NotFound {
   173  		t.Error("got IsNotExist false, expected true")
   174  	}
   175  }
   176  
   177  func testString(t *testing.T, newHarness HarnessMaker) {
   178  	const (
   179  		name    = "test-config-variable"
   180  		content = "hello world"
   181  	)
   182  
   183  	h, err := newHarness(t)
   184  	if err != nil {
   185  		t.Fatal(err)
   186  	}
   187  	defer h.Close()
   188  	ctx := context.Background()
   189  
   190  	if err := h.CreateVariable(ctx, name, []byte(content)); err != nil {
   191  		t.Fatal(err)
   192  	}
   193  	if h.Mutable() {
   194  		defer func() {
   195  			if err := h.DeleteVariable(ctx, name); err != nil {
   196  				t.Fatal(err)
   197  			}
   198  		}()
   199  	}
   200  
   201  	drv, err := h.MakeWatcher(ctx, name, runtimevar.StringDecoder)
   202  	if err != nil {
   203  		t.Fatal(err)
   204  	}
   205  	v := runtimevar.New(drv)
   206  	defer func() {
   207  		if err := v.Close(); err != nil {
   208  			t.Error(err)
   209  		}
   210  	}()
   211  	got, err := v.Watch(ctx)
   212  	if err != nil {
   213  		t.Fatal(err)
   214  	}
   215  	// The variable is decoded to a string and matches the expected content.
   216  	if gotS, ok := got.Value.(string); !ok {
   217  		t.Fatalf("got value of type %T expected string", got.Value)
   218  	} else if gotS != content {
   219  		t.Errorf("got %q want %q", got.Value, content)
   220  	}
   221  
   222  	// A second watch should block forever since the value hasn't changed.
   223  	// A short wait here doesn't guarantee that this is working, but will catch
   224  	// most problems.
   225  	tCtx, cancel := context.WithTimeout(ctx, waitTimeForBlockingCheck())
   226  	defer cancel()
   227  	got, err = v.Watch(tCtx)
   228  	if err == nil {
   229  		t.Errorf("got %v want error", got)
   230  	}
   231  	// tCtx should be cancelled. However, tests using record/replay mode can
   232  	// be in the middle of an RPC when that happens, and save the resulting
   233  	// RPC error during record. During replay, that error can be returned
   234  	// immediately (before tCtx is cancelled). So, we accept deadline exceeded
   235  	// errors as well.
   236  	if tCtx.Err() == nil && !deadlineExceeded(err) {
   237  		t.Errorf("got err %v; want Watch to have blocked until context was Done, or for the error to be deadline exceeded", err)
   238  	}
   239  }
   240  
   241  // Message is used as a target for JSON decoding.
   242  type Message struct {
   243  	Name, Text string
   244  }
   245  
   246  func testJSON(t *testing.T, newHarness HarnessMaker) {
   247  	const (
   248  		name        = "test-config-variable"
   249  		jsonContent = `[
   250  {"Name": "Ed", "Text": "Knock knock."},
   251  {"Name": "Sam", "Text": "Who's there?"}
   252  ]`
   253  	)
   254  	want := []*Message{{Name: "Ed", Text: "Knock knock."}, {Name: "Sam", Text: "Who's there?"}}
   255  
   256  	h, err := newHarness(t)
   257  	if err != nil {
   258  		t.Fatal(err)
   259  	}
   260  	defer h.Close()
   261  	ctx := context.Background()
   262  
   263  	if err := h.CreateVariable(ctx, name, []byte(jsonContent)); err != nil {
   264  		t.Fatal(err)
   265  	}
   266  	if h.Mutable() {
   267  		defer func() {
   268  			if err := h.DeleteVariable(ctx, name); err != nil {
   269  				t.Fatal(err)
   270  			}
   271  		}()
   272  	}
   273  
   274  	var jsonData []*Message
   275  	drv, err := h.MakeWatcher(ctx, name, runtimevar.NewDecoder(jsonData, runtimevar.JSONDecode))
   276  	if err != nil {
   277  		t.Fatal(err)
   278  	}
   279  	v := runtimevar.New(drv)
   280  	defer func() {
   281  		if err := v.Close(); err != nil {
   282  			t.Error(err)
   283  		}
   284  	}()
   285  	got, err := v.Watch(ctx)
   286  	if err != nil {
   287  		t.Fatal(err)
   288  	}
   289  	// The variable is decoded to a []*Message and matches the expected content.
   290  	if gotSlice, ok := got.Value.([]*Message); !ok {
   291  		t.Fatalf("got value of type %T expected []*Message", got.Value)
   292  	} else if !cmp.Equal(gotSlice, want) {
   293  		t.Errorf("got %v want %v", gotSlice, want)
   294  	}
   295  }
   296  
   297  func testInvalidJSON(t *testing.T, newHarness HarnessMaker) {
   298  	const (
   299  		name    = "test-config-variable"
   300  		content = "not-json"
   301  	)
   302  
   303  	h, err := newHarness(t)
   304  	if err != nil {
   305  		t.Fatal(err)
   306  	}
   307  	defer h.Close()
   308  	ctx := context.Background()
   309  
   310  	if err := h.CreateVariable(ctx, name, []byte(content)); err != nil {
   311  		t.Fatal(err)
   312  	}
   313  	if h.Mutable() {
   314  		defer func() {
   315  			if err := h.DeleteVariable(ctx, name); err != nil {
   316  				t.Fatal(err)
   317  			}
   318  		}()
   319  	}
   320  
   321  	var jsonData []*Message
   322  	drv, err := h.MakeWatcher(ctx, name, runtimevar.NewDecoder(jsonData, runtimevar.JSONDecode))
   323  	if err != nil {
   324  		t.Fatal(err)
   325  	}
   326  	v := runtimevar.New(drv)
   327  	defer func() {
   328  		if err := v.Close(); err != nil {
   329  			t.Error(err)
   330  		}
   331  	}()
   332  	got, err := v.Watch(ctx)
   333  	if err == nil {
   334  		t.Errorf("got %v wanted invalid-json error", got.Value)
   335  	}
   336  }
   337  
   338  func testUpdate(t *testing.T, newHarness HarnessMaker) {
   339  	const (
   340  		name     = "test-config-variable"
   341  		content1 = "hello world"
   342  		content2 = "goodbye world"
   343  	)
   344  
   345  	h, err := newHarness(t)
   346  	if err != nil {
   347  		t.Fatal(err)
   348  	}
   349  	defer h.Close()
   350  	if !h.Mutable() {
   351  		return
   352  	}
   353  	ctx := context.Background()
   354  
   355  	// Create the variable and verify WatchVariable sees the value.
   356  	if err := h.CreateVariable(ctx, name, []byte(content1)); err != nil {
   357  		t.Fatal(err)
   358  	}
   359  	defer func() {
   360  		if err := h.DeleteVariable(ctx, name); err != nil {
   361  			t.Fatal(err)
   362  		}
   363  	}()
   364  
   365  	drv, err := h.MakeWatcher(ctx, name, runtimevar.StringDecoder)
   366  	if err != nil {
   367  		t.Fatal(err)
   368  	}
   369  	defer func() {
   370  		if err := drv.Close(); err != nil {
   371  			t.Error(err)
   372  		}
   373  	}()
   374  	state, _ := drv.WatchVariable(ctx, nil)
   375  	if state == nil {
   376  		t.Fatalf("got nil state, want a non-nil state with a value")
   377  	}
   378  	got, err := state.Value()
   379  	if err != nil {
   380  		t.Fatal(err)
   381  	}
   382  	if gotS, ok := got.(string); !ok {
   383  		t.Fatalf("got value of type %T expected string", got)
   384  	} else if gotS != content1 {
   385  		t.Errorf("got %q want %q", got, content1)
   386  	}
   387  
   388  	// The variable hasn't changed, so drv.WatchVariable should either
   389  	// return nil or block.
   390  	cancelCtx, cancel := context.WithTimeout(ctx, waitTimeForBlockingCheck())
   391  	defer cancel()
   392  	unchangedState, _ := drv.WatchVariable(cancelCtx, state)
   393  	if unchangedState == nil {
   394  		// OK
   395  	} else {
   396  		got, err = unchangedState.Value()
   397  		if err != context.DeadlineExceeded {
   398  			t.Fatalf("got state %v/%v/%v, wanted nil or nil/DeadlineExceeded after no change", got, err, gcerrors.Code(err))
   399  		}
   400  	}
   401  
   402  	// Update the variable and verify WatchVariable sees the updated value.
   403  	if err := h.UpdateVariable(ctx, name, []byte(content2)); err != nil {
   404  		t.Fatal(err)
   405  	}
   406  	state, _ = drv.WatchVariable(ctx, state)
   407  	if state == nil {
   408  		t.Fatalf("got nil state, want a non-nil state with a value")
   409  	}
   410  	got, err = state.Value()
   411  	if err != nil {
   412  		t.Fatal(err)
   413  	}
   414  	if gotS, ok := got.(string); !ok {
   415  		t.Fatalf("got value of type %T expected string", got)
   416  	} else if gotS != content2 {
   417  		t.Errorf("got %q want %q", got, content2)
   418  	}
   419  }
   420  
   421  func testDelete(t *testing.T, newHarness HarnessMaker) {
   422  	const (
   423  		name     = "test-config-variable"
   424  		content1 = "hello world"
   425  		content2 = "goodbye world"
   426  	)
   427  
   428  	h, err := newHarness(t)
   429  	if err != nil {
   430  		t.Fatal(err)
   431  	}
   432  	defer h.Close()
   433  	if !h.Mutable() {
   434  		return
   435  	}
   436  	ctx := context.Background()
   437  
   438  	// Create the variable and verify WatchVariable sees the value.
   439  	if err := h.CreateVariable(ctx, name, []byte(content1)); err != nil {
   440  		t.Fatal(err)
   441  	}
   442  	needToDelete := true
   443  	defer func() {
   444  		if needToDelete {
   445  			if err := h.DeleteVariable(ctx, name); err != nil {
   446  				t.Fatal(err)
   447  			}
   448  		}
   449  	}()
   450  
   451  	drv, err := h.MakeWatcher(ctx, name, runtimevar.StringDecoder)
   452  	if err != nil {
   453  		t.Fatal(err)
   454  	}
   455  	defer func() {
   456  		if err := drv.Close(); err != nil {
   457  			t.Error(err)
   458  		}
   459  	}()
   460  	state, _ := drv.WatchVariable(ctx, nil)
   461  	if state == nil {
   462  		t.Fatalf("got nil state, want a non-nil state with a value")
   463  	}
   464  	got, err := state.Value()
   465  	if err != nil {
   466  		t.Fatal(err)
   467  	}
   468  	if gotS, ok := got.(string); !ok {
   469  		t.Fatalf("got value of type %T expected string", got)
   470  	} else if gotS != content1 {
   471  		t.Errorf("got %q want %q", got, content1)
   472  	}
   473  	prev := state
   474  
   475  	// Delete the variable.
   476  	if err := h.DeleteVariable(ctx, name); err != nil {
   477  		t.Fatal(err)
   478  	}
   479  	needToDelete = false
   480  
   481  	// WatchVariable should return a state with an error now.
   482  	state, _ = drv.WatchVariable(ctx, state)
   483  	if state == nil {
   484  		t.Fatalf("got nil state, want a non-nil state with an error")
   485  	}
   486  	got, err = state.Value()
   487  	if err == nil {
   488  		t.Fatalf("got %v want error because variable is deleted", got)
   489  	}
   490  
   491  	// Reset the variable with new content and verify via WatchVariable.
   492  	if err := h.CreateVariable(ctx, name, []byte(content2)); err != nil {
   493  		t.Fatal(err)
   494  	}
   495  	needToDelete = true
   496  	state, _ = drv.WatchVariable(ctx, state)
   497  	if state == nil {
   498  		t.Fatalf("got nil state, want a non-nil state with a value")
   499  	}
   500  	got, err = state.Value()
   501  	if err != nil {
   502  		t.Fatal(err)
   503  	}
   504  	if gotS, ok := got.(string); !ok {
   505  		t.Fatalf("got value of type %T expected string", got)
   506  	} else if gotS != content2 {
   507  		t.Errorf("got %q want %q", got, content2)
   508  	}
   509  	if state.UpdateTime().Before(prev.UpdateTime()) {
   510  		t.Errorf("got UpdateTime %v < previous %v, want >=", state.UpdateTime(), prev.UpdateTime())
   511  	}
   512  }
   513  
   514  func testUpdateWithErrors(t *testing.T, newHarness HarnessMaker) {
   515  	const (
   516  		name     = "test-updating-variable-to-error"
   517  		content1 = `[{"Name": "Foo", "Text": "Bar"}]`
   518  		content2 = "invalid-json"
   519  		content3 = "invalid-json2"
   520  	)
   521  	want := []*Message{{Name: "Foo", Text: "Bar"}}
   522  
   523  	h, err := newHarness(t)
   524  	if err != nil {
   525  		t.Fatal(err)
   526  	}
   527  	defer h.Close()
   528  	if !h.Mutable() {
   529  		return
   530  	}
   531  	ctx := context.Background()
   532  
   533  	// Create the variable and verify WatchVariable sees the value.
   534  	if err := h.CreateVariable(ctx, name, []byte(content1)); err != nil {
   535  		t.Fatal(err)
   536  	}
   537  	defer func() {
   538  		if err := h.DeleteVariable(ctx, name); err != nil {
   539  			t.Fatal(err)
   540  		}
   541  	}()
   542  
   543  	var jsonData []*Message
   544  	drv, err := h.MakeWatcher(ctx, name, runtimevar.NewDecoder(jsonData, runtimevar.JSONDecode))
   545  	if err != nil {
   546  		t.Fatal(err)
   547  	}
   548  	defer func() {
   549  		if err := drv.Close(); err != nil {
   550  			t.Error(err)
   551  		}
   552  	}()
   553  	state, _ := drv.WatchVariable(ctx, nil)
   554  	if state == nil {
   555  		t.Fatal("got nil state, want a non-nil state with a value")
   556  	}
   557  	got, err := state.Value()
   558  	if err != nil {
   559  		t.Fatal(err)
   560  	}
   561  	if gotSlice, ok := got.([]*Message); !ok {
   562  		t.Fatalf("got value of type %T expected []*Message", got)
   563  	} else if !cmp.Equal(gotSlice, want) {
   564  		t.Errorf("got %v want %v", gotSlice, want)
   565  	}
   566  
   567  	// Update the variable to invalid JSON and verify WatchVariable returns an error.
   568  	if err := h.UpdateVariable(ctx, name, []byte(content2)); err != nil {
   569  		t.Fatal(err)
   570  	}
   571  	state, _ = drv.WatchVariable(ctx, state)
   572  	if state == nil {
   573  		t.Fatal("got nil state, want a non-nil state with an error")
   574  	}
   575  	_, err = state.Value()
   576  	if err == nil {
   577  		t.Fatal("got nil err want invalid JSON error")
   578  	}
   579  
   580  	// Update the variable again, with different invalid JSON.
   581  	// WatchVariable should block or return nil since it's the same error as before.
   582  	if err := h.UpdateVariable(ctx, name, []byte(content3)); err != nil {
   583  		t.Fatal(err)
   584  	}
   585  	tCtx, cancel := context.WithTimeout(ctx, waitTimeForBlockingCheck())
   586  	defer cancel()
   587  	state, _ = drv.WatchVariable(tCtx, state)
   588  	if state == nil {
   589  		// OK: nil indicates no change.
   590  	} else {
   591  		// WatchVariable should have blocked until tCtx was cancelled, and we
   592  		// should have gotten that error back.
   593  		got, err := state.Value()
   594  		if err == nil {
   595  			t.Fatalf("got %v and nil error, want non-nil error", got)
   596  		}
   597  		// tCtx should be cancelled. However, tests using record/replay mode can
   598  		// be in the middle of an RPC when that happens, and save the resulting
   599  		// RPC error during record. During replay, that error can be returned
   600  		// immediately (before tCtx is cancelled). So, we accept deadline exceeded
   601  		// errors as well.
   602  		if tCtx.Err() == nil && !deadlineExceeded(err) {
   603  			t.Errorf("got err %v; want Watch to have blocked until context was Done, or for the error to be deadline exceeded", err)
   604  		}
   605  	}
   606  }
   607  
   608  // testAs tests the various As functions, using AsTest.
   609  func testAs(t *testing.T, newHarness HarnessMaker, st AsTest) {
   610  	const (
   611  		name    = "variable-for-as"
   612  		content = "hello world"
   613  	)
   614  
   615  	h, err := newHarness(t)
   616  	if err != nil {
   617  		t.Fatal(err)
   618  	}
   619  	defer h.Close()
   620  	ctx := context.Background()
   621  
   622  	// Try to read the variable before it exists.
   623  	drv, err := h.MakeWatcher(ctx, name, runtimevar.StringDecoder)
   624  	if err != nil {
   625  		t.Fatal(err)
   626  	}
   627  	v := runtimevar.New(drv)
   628  	s, gotErr := v.Watch(ctx)
   629  	if gotErr == nil {
   630  		t.Fatalf("got nil error and %v, expected non-nil error", v)
   631  	}
   632  	if err := st.ErrorCheck(v, gotErr); err != nil {
   633  		t.Error(err)
   634  	}
   635  	var dummy string
   636  	if s.As(&dummy) {
   637  		t.Error(errors.New("want Snapshot.As to return false when Snapshot is zero value"))
   638  	}
   639  	if err := v.Close(); err != nil {
   640  		t.Error(err)
   641  	}
   642  
   643  	// Create the variable and verify WatchVariable sees the value.
   644  	if err := h.CreateVariable(ctx, name, []byte(content)); err != nil {
   645  		t.Fatal(err)
   646  	}
   647  	if h.Mutable() {
   648  		defer func() {
   649  			if err := h.DeleteVariable(ctx, name); err != nil {
   650  				t.Fatal(err)
   651  			}
   652  		}()
   653  	}
   654  
   655  	drv, err = h.MakeWatcher(ctx, name, runtimevar.StringDecoder)
   656  	if err != nil {
   657  		t.Fatal(err)
   658  	}
   659  	v = runtimevar.New(drv)
   660  	defer func() {
   661  		if err := v.Close(); err != nil {
   662  			t.Error(err)
   663  		}
   664  	}()
   665  	s, err = v.Watch(ctx)
   666  	if err != nil {
   667  		t.Fatal(err)
   668  	}
   669  	if err := st.SnapshotCheck(&s); err != nil {
   670  		t.Error(err)
   671  	}
   672  }