github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/views/hook_ui_test.go (about)

     1  package views
     2  
     3  import (
     4  	"fmt"
     5  	"regexp"
     6  	"testing"
     7  	"time"
     8  
     9  	"strings"
    10  
    11  	"github.com/zclconf/go-cty/cty"
    12  
    13  	"github.com/hashicorp/terraform/internal/addrs"
    14  	"github.com/hashicorp/terraform/internal/command/arguments"
    15  	"github.com/hashicorp/terraform/internal/plans"
    16  	"github.com/hashicorp/terraform/internal/providers"
    17  	"github.com/hashicorp/terraform/internal/states"
    18  	"github.com/hashicorp/terraform/internal/terminal"
    19  	"github.com/hashicorp/terraform/internal/terraform"
    20  )
    21  
    22  // Test the PreApply hook for creating a new resource
    23  func TestUiHookPreApply_create(t *testing.T) {
    24  	streams, done := terminal.StreamsForTesting(t)
    25  	view := NewView(streams)
    26  	h := NewUiHook(view)
    27  	h.resources = map[string]uiResourceState{
    28  		"test_instance.foo": {
    29  			Op:    uiResourceCreate,
    30  			Start: time.Now(),
    31  		},
    32  	}
    33  
    34  	addr := addrs.Resource{
    35  		Mode: addrs.ManagedResourceMode,
    36  		Type: "test_instance",
    37  		Name: "foo",
    38  	}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
    39  
    40  	priorState := cty.NullVal(cty.Object(map[string]cty.Type{
    41  		"id":  cty.String,
    42  		"bar": cty.List(cty.String),
    43  	}))
    44  	plannedNewState := cty.ObjectVal(map[string]cty.Value{
    45  		"id": cty.StringVal("test"),
    46  		"bar": cty.ListVal([]cty.Value{
    47  			cty.StringVal("baz"),
    48  		}),
    49  	})
    50  
    51  	action, err := h.PreApply(addr, states.CurrentGen, plans.Create, priorState, plannedNewState)
    52  	if err != nil {
    53  		t.Fatal(err)
    54  	}
    55  	if action != terraform.HookActionContinue {
    56  		t.Fatalf("Expected hook to continue, given: %#v", action)
    57  	}
    58  
    59  	// stop the background writer
    60  	uiState := h.resources[addr.String()]
    61  	close(uiState.DoneCh)
    62  	<-uiState.done
    63  
    64  	expectedOutput := "test_instance.foo: Creating...\n"
    65  	result := done(t)
    66  	output := result.Stdout()
    67  	if output != expectedOutput {
    68  		t.Fatalf("Output didn't match.\nExpected: %q\nGiven: %q", expectedOutput, output)
    69  	}
    70  
    71  	expectedErrOutput := ""
    72  	errOutput := result.Stderr()
    73  	if errOutput != expectedErrOutput {
    74  		t.Fatalf("Error output didn't match.\nExpected: %q\nGiven: %q", expectedErrOutput, errOutput)
    75  	}
    76  }
    77  
    78  // Test the PreApply hook's use of a periodic timer to display "still working"
    79  // log lines
    80  func TestUiHookPreApply_periodicTimer(t *testing.T) {
    81  	streams, done := terminal.StreamsForTesting(t)
    82  	view := NewView(streams)
    83  	h := NewUiHook(view)
    84  	h.periodicUiTimer = 1 * time.Second
    85  	h.resources = map[string]uiResourceState{
    86  		"test_instance.foo": {
    87  			Op:    uiResourceModify,
    88  			Start: time.Now(),
    89  		},
    90  	}
    91  
    92  	addr := addrs.Resource{
    93  		Mode: addrs.ManagedResourceMode,
    94  		Type: "test_instance",
    95  		Name: "foo",
    96  	}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
    97  
    98  	priorState := cty.ObjectVal(map[string]cty.Value{
    99  		"id":  cty.StringVal("test"),
   100  		"bar": cty.ListValEmpty(cty.String),
   101  	})
   102  	plannedNewState := cty.ObjectVal(map[string]cty.Value{
   103  		"id": cty.StringVal("test"),
   104  		"bar": cty.ListVal([]cty.Value{
   105  			cty.StringVal("baz"),
   106  		}),
   107  	})
   108  
   109  	action, err := h.PreApply(addr, states.CurrentGen, plans.Update, priorState, plannedNewState)
   110  	if err != nil {
   111  		t.Fatal(err)
   112  	}
   113  	if action != terraform.HookActionContinue {
   114  		t.Fatalf("Expected hook to continue, given: %#v", action)
   115  	}
   116  
   117  	time.Sleep(3100 * time.Millisecond)
   118  
   119  	// stop the background writer
   120  	uiState := h.resources[addr.String()]
   121  	close(uiState.DoneCh)
   122  	<-uiState.done
   123  
   124  	expectedOutput := `test_instance.foo: Modifying... [id=test]
   125  test_instance.foo: Still modifying... [id=test, 1s elapsed]
   126  test_instance.foo: Still modifying... [id=test, 2s elapsed]
   127  test_instance.foo: Still modifying... [id=test, 3s elapsed]
   128  `
   129  	result := done(t)
   130  	output := result.Stdout()
   131  	if output != expectedOutput {
   132  		t.Fatalf("Output didn't match.\nExpected: %q\nGiven: %q", expectedOutput, output)
   133  	}
   134  
   135  	expectedErrOutput := ""
   136  	errOutput := result.Stderr()
   137  	if errOutput != expectedErrOutput {
   138  		t.Fatalf("Error output didn't match.\nExpected: %q\nGiven: %q", expectedErrOutput, errOutput)
   139  	}
   140  }
   141  
   142  // Test the PreApply hook's destroy path, including passing a deposed key as
   143  // the gen argument.
   144  func TestUiHookPreApply_destroy(t *testing.T) {
   145  	streams, done := terminal.StreamsForTesting(t)
   146  	view := NewView(streams)
   147  	h := NewUiHook(view)
   148  	h.resources = map[string]uiResourceState{
   149  		"test_instance.foo": {
   150  			Op:    uiResourceDestroy,
   151  			Start: time.Now(),
   152  		},
   153  	}
   154  
   155  	addr := addrs.Resource{
   156  		Mode: addrs.ManagedResourceMode,
   157  		Type: "test_instance",
   158  		Name: "foo",
   159  	}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
   160  
   161  	priorState := cty.ObjectVal(map[string]cty.Value{
   162  		"id": cty.StringVal("abc123"),
   163  		"verbs": cty.ListVal([]cty.Value{
   164  			cty.StringVal("boop"),
   165  		}),
   166  	})
   167  	plannedNewState := cty.NullVal(cty.Object(map[string]cty.Type{
   168  		"id":    cty.String,
   169  		"verbs": cty.List(cty.String),
   170  	}))
   171  
   172  	key := states.NewDeposedKey()
   173  	action, err := h.PreApply(addr, key, plans.Delete, priorState, plannedNewState)
   174  	if err != nil {
   175  		t.Fatal(err)
   176  	}
   177  	if action != terraform.HookActionContinue {
   178  		t.Fatalf("Expected hook to continue, given: %#v", action)
   179  	}
   180  
   181  	// stop the background writer
   182  	uiState := h.resources[addr.String()]
   183  	close(uiState.DoneCh)
   184  	<-uiState.done
   185  
   186  	result := done(t)
   187  	expectedOutput := fmt.Sprintf("test_instance.foo (deposed object %s): Destroying... [id=abc123]\n", key)
   188  	output := result.Stdout()
   189  	if output != expectedOutput {
   190  		t.Fatalf("Output didn't match.\nExpected: %q\nGiven: %q", expectedOutput, output)
   191  	}
   192  
   193  	expectedErrOutput := ""
   194  	errOutput := result.Stderr()
   195  	if errOutput != expectedErrOutput {
   196  		t.Fatalf("Error output didn't match.\nExpected: %q\nGiven: %q", expectedErrOutput, errOutput)
   197  	}
   198  }
   199  
   200  // Verify that colorize is called on format strings, not user input, by adding
   201  // valid color codes as resource names and IDs.
   202  func TestUiHookPostApply_colorInterpolation(t *testing.T) {
   203  	streams, done := terminal.StreamsForTesting(t)
   204  	view := NewView(streams)
   205  	view.Configure(&arguments.View{NoColor: false})
   206  	h := NewUiHook(view)
   207  	h.resources = map[string]uiResourceState{
   208  		"test_instance.foo[\"[red]\"]": {
   209  			Op:    uiResourceCreate,
   210  			Start: time.Now(),
   211  		},
   212  	}
   213  
   214  	addr := addrs.Resource{
   215  		Mode: addrs.ManagedResourceMode,
   216  		Type: "test_instance",
   217  		Name: "foo",
   218  	}.Instance(addrs.StringKey("[red]")).Absolute(addrs.RootModuleInstance)
   219  
   220  	newState := cty.ObjectVal(map[string]cty.Value{
   221  		"id": cty.StringVal("[blue]"),
   222  	})
   223  
   224  	action, err := h.PostApply(addr, states.CurrentGen, newState, nil)
   225  	if err != nil {
   226  		t.Fatal(err)
   227  	}
   228  	if action != terraform.HookActionContinue {
   229  		t.Fatalf("Expected hook to continue, given: %#v", action)
   230  	}
   231  	result := done(t)
   232  
   233  	reset := "\x1b[0m"
   234  	bold := "\x1b[1m"
   235  	wantPrefix := reset + bold + `test_instance.foo["[red]"]: Creation complete after`
   236  	wantSuffix := "[id=[blue]]" + reset + "\n"
   237  	output := result.Stdout()
   238  
   239  	if !strings.HasPrefix(output, wantPrefix) {
   240  		t.Fatalf("wrong output prefix\n got: %#v\nwant: %#v", output, wantPrefix)
   241  	}
   242  
   243  	if !strings.HasSuffix(output, wantSuffix) {
   244  		t.Fatalf("wrong output suffix\n got: %#v\nwant: %#v", output, wantSuffix)
   245  	}
   246  
   247  	expectedErrOutput := ""
   248  	errOutput := result.Stderr()
   249  	if errOutput != expectedErrOutput {
   250  		t.Fatalf("Error output didn't match.\nExpected: %q\nGiven: %q", expectedErrOutput, errOutput)
   251  	}
   252  }
   253  
   254  // Test that the PostApply hook renders a total time.
   255  func TestUiHookPostApply_emptyState(t *testing.T) {
   256  	streams, done := terminal.StreamsForTesting(t)
   257  	view := NewView(streams)
   258  	h := NewUiHook(view)
   259  	h.resources = map[string]uiResourceState{
   260  		"data.google_compute_zones.available": {
   261  			Op:    uiResourceDestroy,
   262  			Start: time.Now(),
   263  		},
   264  	}
   265  
   266  	addr := addrs.Resource{
   267  		Mode: addrs.DataResourceMode,
   268  		Type: "google_compute_zones",
   269  		Name: "available",
   270  	}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
   271  
   272  	newState := cty.NullVal(cty.Object(map[string]cty.Type{
   273  		"id":    cty.String,
   274  		"names": cty.List(cty.String),
   275  	}))
   276  
   277  	action, err := h.PostApply(addr, states.CurrentGen, newState, nil)
   278  	if err != nil {
   279  		t.Fatal(err)
   280  	}
   281  	if action != terraform.HookActionContinue {
   282  		t.Fatalf("Expected hook to continue, given: %#v", action)
   283  	}
   284  	result := done(t)
   285  
   286  	expectedRegexp := "^data.google_compute_zones.available: Destruction complete after -?[a-z0-9µ.]+\n$"
   287  	output := result.Stdout()
   288  	if matched, _ := regexp.MatchString(expectedRegexp, output); !matched {
   289  		t.Fatalf("Output didn't match regexp.\nExpected: %q\nGiven: %q", expectedRegexp, output)
   290  	}
   291  
   292  	expectedErrOutput := ""
   293  	errOutput := result.Stderr()
   294  	if errOutput != expectedErrOutput {
   295  		t.Fatalf("Error output didn't match.\nExpected: %q\nGiven: %q", expectedErrOutput, errOutput)
   296  	}
   297  }
   298  
   299  func TestPreProvisionInstanceStep(t *testing.T) {
   300  	streams, done := terminal.StreamsForTesting(t)
   301  	view := NewView(streams)
   302  	h := NewUiHook(view)
   303  
   304  	addr := addrs.Resource{
   305  		Mode: addrs.ManagedResourceMode,
   306  		Type: "test_instance",
   307  		Name: "foo",
   308  	}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
   309  
   310  	action, err := h.PreProvisionInstanceStep(addr, "local-exec")
   311  	if err != nil {
   312  		t.Fatal(err)
   313  	}
   314  	if action != terraform.HookActionContinue {
   315  		t.Fatalf("Expected hook to continue, given: %#v", action)
   316  	}
   317  	result := done(t)
   318  
   319  	if got, want := result.Stdout(), "test_instance.foo: Provisioning with 'local-exec'...\n"; got != want {
   320  		t.Fatalf("unexpected output\n got: %q\nwant: %q", got, want)
   321  	}
   322  }
   323  
   324  // Test ProvisionOutput, including lots of edge cases for the output
   325  // whitespace/line ending logic.
   326  func TestProvisionOutput(t *testing.T) {
   327  	addr := addrs.Resource{
   328  		Mode: addrs.ManagedResourceMode,
   329  		Type: "test_instance",
   330  		Name: "foo",
   331  	}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
   332  
   333  	testCases := map[string]struct {
   334  		provisioner string
   335  		input       string
   336  		wantOutput  string
   337  	}{
   338  		"single line": {
   339  			"local-exec",
   340  			"foo\n",
   341  			"test_instance.foo (local-exec): foo\n",
   342  		},
   343  		"multiple lines": {
   344  			"x",
   345  			`foo
   346  bar
   347  baz
   348  `,
   349  			`test_instance.foo (x): foo
   350  test_instance.foo (x): bar
   351  test_instance.foo (x): baz
   352  `,
   353  		},
   354  		"trailing whitespace": {
   355  			"x",
   356  			"foo                  \nbar\n",
   357  			"test_instance.foo (x): foo\ntest_instance.foo (x): bar\n",
   358  		},
   359  		"blank lines": {
   360  			"x",
   361  			"foo\n\nbar\n\n\nbaz\n",
   362  			`test_instance.foo (x): foo
   363  test_instance.foo (x): bar
   364  test_instance.foo (x): baz
   365  `,
   366  		},
   367  		"no final newline": {
   368  			"x",
   369  			`foo
   370  bar`,
   371  			`test_instance.foo (x): foo
   372  test_instance.foo (x): bar
   373  `,
   374  		},
   375  		"CR, no LF": {
   376  			"MacOS 9?",
   377  			"foo\rbar\r",
   378  			`test_instance.foo (MacOS 9?): foo
   379  test_instance.foo (MacOS 9?): bar
   380  `,
   381  		},
   382  		"CRLF": {
   383  			"winrm",
   384  			"foo\r\nbar\r\n",
   385  			`test_instance.foo (winrm): foo
   386  test_instance.foo (winrm): bar
   387  `,
   388  		},
   389  	}
   390  
   391  	for name, tc := range testCases {
   392  		t.Run(name, func(t *testing.T) {
   393  			streams, done := terminal.StreamsForTesting(t)
   394  			view := NewView(streams)
   395  			h := NewUiHook(view)
   396  
   397  			h.ProvisionOutput(addr, tc.provisioner, tc.input)
   398  			result := done(t)
   399  
   400  			if got := result.Stdout(); got != tc.wantOutput {
   401  				t.Fatalf("unexpected output\n got: %q\nwant: %q", got, tc.wantOutput)
   402  			}
   403  		})
   404  	}
   405  }
   406  
   407  // Test the PreRefresh hook in the normal path where the resource exists with
   408  // an ID key and value in the state.
   409  func TestPreRefresh(t *testing.T) {
   410  	streams, done := terminal.StreamsForTesting(t)
   411  	view := NewView(streams)
   412  	h := NewUiHook(view)
   413  
   414  	addr := addrs.Resource{
   415  		Mode: addrs.ManagedResourceMode,
   416  		Type: "test_instance",
   417  		Name: "foo",
   418  	}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
   419  
   420  	priorState := cty.ObjectVal(map[string]cty.Value{
   421  		"id":  cty.StringVal("test"),
   422  		"bar": cty.ListValEmpty(cty.String),
   423  	})
   424  
   425  	action, err := h.PreRefresh(addr, states.CurrentGen, priorState)
   426  
   427  	if err != nil {
   428  		t.Fatal(err)
   429  	}
   430  	if action != terraform.HookActionContinue {
   431  		t.Fatalf("Expected hook to continue, given: %#v", action)
   432  	}
   433  	result := done(t)
   434  
   435  	if got, want := result.Stdout(), "test_instance.foo: Refreshing state... [id=test]\n"; got != want {
   436  		t.Fatalf("unexpected output\n got: %q\nwant: %q", got, want)
   437  	}
   438  }
   439  
   440  // Test that PreRefresh still works if no ID key and value can be determined
   441  // from state.
   442  func TestPreRefresh_noID(t *testing.T) {
   443  	streams, done := terminal.StreamsForTesting(t)
   444  	view := NewView(streams)
   445  	h := NewUiHook(view)
   446  
   447  	addr := addrs.Resource{
   448  		Mode: addrs.ManagedResourceMode,
   449  		Type: "test_instance",
   450  		Name: "foo",
   451  	}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
   452  
   453  	priorState := cty.ObjectVal(map[string]cty.Value{
   454  		"bar": cty.ListValEmpty(cty.String),
   455  	})
   456  
   457  	action, err := h.PreRefresh(addr, states.CurrentGen, priorState)
   458  
   459  	if err != nil {
   460  		t.Fatal(err)
   461  	}
   462  	if action != terraform.HookActionContinue {
   463  		t.Fatalf("Expected hook to continue, given: %#v", action)
   464  	}
   465  	result := done(t)
   466  
   467  	if got, want := result.Stdout(), "test_instance.foo: Refreshing state...\n"; got != want {
   468  		t.Fatalf("unexpected output\n got: %q\nwant: %q", got, want)
   469  	}
   470  }
   471  
   472  // Test the very simple PreImportState hook.
   473  func TestPreImportState(t *testing.T) {
   474  	streams, done := terminal.StreamsForTesting(t)
   475  	view := NewView(streams)
   476  	h := NewUiHook(view)
   477  
   478  	addr := addrs.Resource{
   479  		Mode: addrs.ManagedResourceMode,
   480  		Type: "test_instance",
   481  		Name: "foo",
   482  	}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
   483  
   484  	action, err := h.PreImportState(addr, "test")
   485  
   486  	if err != nil {
   487  		t.Fatal(err)
   488  	}
   489  	if action != terraform.HookActionContinue {
   490  		t.Fatalf("Expected hook to continue, given: %#v", action)
   491  	}
   492  	result := done(t)
   493  
   494  	if got, want := result.Stdout(), "test_instance.foo: Importing from ID \"test\"...\n"; got != want {
   495  		t.Fatalf("unexpected output\n got: %q\nwant: %q", got, want)
   496  	}
   497  }
   498  
   499  // Test the PostImportState UI hook. Again, this hook behaviour seems odd to
   500  // me (see below), so please don't consider these tests as justification for
   501  // keeping this behaviour.
   502  func TestPostImportState(t *testing.T) {
   503  	streams, done := terminal.StreamsForTesting(t)
   504  	view := NewView(streams)
   505  	h := NewUiHook(view)
   506  
   507  	addr := addrs.Resource{
   508  		Mode: addrs.ManagedResourceMode,
   509  		Type: "test_instance",
   510  		Name: "foo",
   511  	}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
   512  
   513  	// The "Prepared [...] for import" lines display the type name of each of
   514  	// the imported resources passed to the hook. I'm not sure how it's
   515  	// possible for an import to result in a different resource type name than
   516  	// the target address, but the hook works like this so we're covering it.
   517  	imported := []providers.ImportedResource{
   518  		{
   519  			TypeName: "test_some_instance",
   520  			State: cty.ObjectVal(map[string]cty.Value{
   521  				"id": cty.StringVal("test"),
   522  			}),
   523  		},
   524  		{
   525  			TypeName: "test_other_instance",
   526  			State: cty.ObjectVal(map[string]cty.Value{
   527  				"id": cty.StringVal("test"),
   528  			}),
   529  		},
   530  	}
   531  
   532  	action, err := h.PostImportState(addr, imported)
   533  
   534  	if err != nil {
   535  		t.Fatal(err)
   536  	}
   537  	if action != terraform.HookActionContinue {
   538  		t.Fatalf("Expected hook to continue, given: %#v", action)
   539  	}
   540  	result := done(t)
   541  
   542  	want := `test_instance.foo: Import prepared!
   543    Prepared test_some_instance for import
   544    Prepared test_other_instance for import
   545  `
   546  	if got := result.Stdout(); got != want {
   547  		t.Fatalf("unexpected output\n got: %q\nwant: %q", got, want)
   548  	}
   549  }
   550  
   551  func TestTruncateId(t *testing.T) {
   552  	testCases := []struct {
   553  		Input    string
   554  		Expected string
   555  		MaxLen   int
   556  	}{
   557  		{
   558  			Input:    "Hello world",
   559  			Expected: "H...d",
   560  			MaxLen:   3,
   561  		},
   562  		{
   563  			Input:    "Hello world",
   564  			Expected: "H...d",
   565  			MaxLen:   5,
   566  		},
   567  		{
   568  			Input:    "Hello world",
   569  			Expected: "He...d",
   570  			MaxLen:   6,
   571  		},
   572  		{
   573  			Input:    "Hello world",
   574  			Expected: "He...ld",
   575  			MaxLen:   7,
   576  		},
   577  		{
   578  			Input:    "Hello world",
   579  			Expected: "Hel...ld",
   580  			MaxLen:   8,
   581  		},
   582  		{
   583  			Input:    "Hello world",
   584  			Expected: "Hel...rld",
   585  			MaxLen:   9,
   586  		},
   587  		{
   588  			Input:    "Hello world",
   589  			Expected: "Hell...rld",
   590  			MaxLen:   10,
   591  		},
   592  		{
   593  			Input:    "Hello world",
   594  			Expected: "Hello world",
   595  			MaxLen:   11,
   596  		},
   597  		{
   598  			Input:    "Hello world",
   599  			Expected: "Hello world",
   600  			MaxLen:   12,
   601  		},
   602  		{
   603  			Input:    "あいうえおかきくけこさ",
   604  			Expected: "あ...さ",
   605  			MaxLen:   3,
   606  		},
   607  		{
   608  			Input:    "あいうえおかきくけこさ",
   609  			Expected: "あ...さ",
   610  			MaxLen:   5,
   611  		},
   612  		{
   613  			Input:    "あいうえおかきくけこさ",
   614  			Expected: "あい...さ",
   615  			MaxLen:   6,
   616  		},
   617  		{
   618  			Input:    "あいうえおかきくけこさ",
   619  			Expected: "あい...こさ",
   620  			MaxLen:   7,
   621  		},
   622  		{
   623  			Input:    "あいうえおかきくけこさ",
   624  			Expected: "あいう...こさ",
   625  			MaxLen:   8,
   626  		},
   627  		{
   628  			Input:    "あいうえおかきくけこさ",
   629  			Expected: "あいう...けこさ",
   630  			MaxLen:   9,
   631  		},
   632  		{
   633  			Input:    "あいうえおかきくけこさ",
   634  			Expected: "あいうえ...けこさ",
   635  			MaxLen:   10,
   636  		},
   637  		{
   638  			Input:    "あいうえおかきくけこさ",
   639  			Expected: "あいうえおかきくけこさ",
   640  			MaxLen:   11,
   641  		},
   642  		{
   643  			Input:    "あいうえおかきくけこさ",
   644  			Expected: "あいうえおかきくけこさ",
   645  			MaxLen:   12,
   646  		},
   647  	}
   648  	for i, tc := range testCases {
   649  		testName := fmt.Sprintf("%d", i)
   650  		t.Run(testName, func(t *testing.T) {
   651  			out := truncateId(tc.Input, tc.MaxLen)
   652  			if out != tc.Expected {
   653  				t.Fatalf("Expected %q to be shortened to %d as %q (given: %q)",
   654  					tc.Input, tc.MaxLen, tc.Expected, out)
   655  			}
   656  		})
   657  	}
   658  }