cuelang.org/go@v0.13.0/internal/core/adt/sched_test.go (about)

     1  // Copyright 2023 CUE 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  //     http://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 adt
    16  
    17  import (
    18  	"fmt"
    19  	"strings"
    20  	"testing"
    21  
    22  	"cuelang.org/go/internal"
    23  	"cuelang.org/go/internal/cuetest"
    24  )
    25  
    26  // These states are used for testing. Each has a suffix of their corresponding
    27  // name in states.go. Debuggers, when resolving constants, will often only
    28  // show the debug constants. Adding the suffix clarifies which states these
    29  // correspond to in the main program.
    30  //
    31  // We could also use the states in the main program directly, but states may
    32  // shift, so this way we ensure that we separate these concerns.
    33  const (
    34  	c1AllAncestorsProcessed condition = 1 << iota
    35  	c2ArcTypeKnown
    36  	c3ValueKnown
    37  	c4ScalarKnown
    38  
    39  	// autoFieldConjunctsKnown is a condition that is automatically set by the simulator.
    40  	autoFieldConjunctsKnown
    41  )
    42  
    43  func TestStateNames(t *testing.T) {
    44  	if c1AllAncestorsProcessed != allAncestorsProcessed {
    45  		t.Error("inconsistent state name for allAncestorsProcessed")
    46  	}
    47  	if c2ArcTypeKnown != arcTypeKnown {
    48  		t.Error("inconsistent state name for arcTypeKnown")
    49  	}
    50  	if c3ValueKnown != valueKnown {
    51  		t.Error("inconsistent state name for valueKnown")
    52  	}
    53  	if c4ScalarKnown != scalarKnown {
    54  		t.Error("inconsistent state name for scalarKnown")
    55  	}
    56  	if autoFieldConjunctsKnown != fieldConjunctsKnown {
    57  		t.Error("inconsistent state name for fieldConjunctsKnown")
    58  	}
    59  }
    60  
    61  // TestScheduler tests the non-CUE specific scheduler functionality.
    62  func TestScheduler(t *testing.T) {
    63  	ctx := &OpContext{
    64  		Version: internal.EvalV2,
    65  		taskContext: taskContext{
    66  			counterMask: c1AllAncestorsProcessed | c2ArcTypeKnown | c3ValueKnown | c4ScalarKnown,
    67  			complete:    func(s *scheduler) condition { return 0 },
    68  		},
    69  	}
    70  
    71  	// shared state
    72  	nodeID := 0
    73  	w := &strings.Builder{}
    74  	nodes := []*nodeContext{}
    75  
    76  	node := func(parent *nodeContext) *nodeContext {
    77  		if nodeID == 0 {
    78  			if parent != nil {
    79  				t.Fatal("root node must be created first")
    80  			}
    81  		} else {
    82  			if parent == nil {
    83  				t.Fatal("non-root node must have parent")
    84  			}
    85  		}
    86  
    87  		n := &nodeContext{scheduler: scheduler{ctx: ctx}, refCount: nodeID}
    88  		nodeID++
    89  		nodes = append(nodes, n)
    90  		return n
    91  	}
    92  
    93  	// dep encodes a dependency on a node uncovered while running a task. It
    94  	// corresponds to a single evaluation of a top-level expression within a
    95  	// task.
    96  	type dep struct {
    97  		node  *nodeContext
    98  		needs condition
    99  	}
   100  
   101  	// process simulates the running of a task with the given dependencies on
   102  	// other nodes/ schedulers.
   103  	//
   104  	// Note that tasks indicate their dependencies at runtime, and that these
   105  	// are not statically declared at the time of task creation. This is because
   106  	// dependencies may only be known after evaluating some CUE. As a
   107  	// consequence, it may be possible for a tasks to be started before one of
   108  	// its dependencies is run. Blocking only occurs if there is a mutual
   109  	// dependency that cannot be resolved without first blocking the task and
   110  	// coming back to it later.
   111  	process := func(name string, t *task, deps ...dep) (ok bool) {
   112  		fmt.Fprintf(w, "\n\t\t    running task %s", name)
   113  		ok = true
   114  		for _, d := range deps {
   115  			func() {
   116  				defer func() {
   117  					if x := recover(); x != nil {
   118  						fmt.Fprintf(w, "\n\t\t        task %s waiting for v%d meeting %x", name, d.node.refCount, d.needs)
   119  						fmt.Fprint(w, ": BLOCKED")
   120  						panic(x)
   121  					}
   122  				}()
   123  				if !d.node.process(d.needs, yield) {
   124  					ok = false
   125  				}
   126  			}()
   127  		}
   128  		return ok
   129  	}
   130  
   131  	// success creates a task that will succeed.
   132  	success := func(name string, n *nodeContext, completes, needs condition, deps ...dep) *task {
   133  		t := &task{
   134  			run: &runner{
   135  				f: func(ctx *OpContext, t *task, mode runMode) {
   136  					process(name, t, deps...)
   137  				},
   138  				completes: completes,
   139  				needs:     needs,
   140  			},
   141  			node: n,
   142  			x:    &String{Str: name}, // Set name for debugging purposes.
   143  		}
   144  		n.insertTask(t)
   145  		return t
   146  	}
   147  
   148  	// signal is a task that unconditionally sets a completion bit.
   149  	signal := func(name string, n *nodeContext, completes condition, deps ...dep) *task {
   150  		t := &task{
   151  			run: &runner{
   152  				f: func(ctx *OpContext, t *task, mode runMode) {
   153  					if process(name, t, deps...) {
   154  						n.scheduler.signal(completes)
   155  					}
   156  				},
   157  				completes: completes,
   158  			},
   159  			node: n,
   160  			x:    &String{Str: name}, // Set name for debugging purposes.
   161  		}
   162  		n.insertTask(t)
   163  		return t
   164  	}
   165  
   166  	// completes creates a task that completes some state in another node.
   167  	completes := func(name string, n, other *nodeContext, completes condition, deps ...dep) *task {
   168  		other.scheduler.incrementCounts(completes)
   169  		t := &task{
   170  			run: &runner{
   171  				f: func(ctx *OpContext, t *task, mode runMode) {
   172  					if process(name, t, deps...) {
   173  						other.scheduler.decrementCounts(completes)
   174  					}
   175  				},
   176  				completes: completes,
   177  			},
   178  			node: n,
   179  			x:    &String{Str: name}, // Set name for debugging purposes.
   180  		}
   181  		n.insertTask(t)
   182  		return t
   183  	}
   184  
   185  	// fail creates a task that will fail.
   186  	fail := func(name string, n *nodeContext, completes, needs condition, deps ...dep) *task {
   187  		t := &task{
   188  
   189  			run: &runner{
   190  				f: func(ctx *OpContext, t *task, mode runMode) {
   191  					fmt.Fprintf(w, "\n\t\t    running task %s:", name)
   192  					t.err = &Bottom{}
   193  					fmt.Fprint(w, " FAIL")
   194  				},
   195  				completes: completes,
   196  				needs:     needs,
   197  			},
   198  			node: n,
   199  			x:    &String{Str: name}, // Set name for debugging purposes.
   200  		}
   201  		n.insertTask(t)
   202  		return t
   203  	}
   204  
   205  	type testCase struct {
   206  		name string
   207  		init func()
   208  
   209  		log   string // A lot
   210  		state string // A textual representation of the task state
   211  	}
   212  
   213  	cases := []testCase{{
   214  		name: "empty scheduler",
   215  		init: func() {
   216  			node(nil)
   217  		},
   218  		log: ``,
   219  
   220  		state: `
   221  			v0 (SUCCESS):`,
   222  	}, {
   223  		name: "node with one task",
   224  		init: func() {
   225  			v0 := node(nil)
   226  			success("t1", v0, c1AllAncestorsProcessed, 0)
   227  		},
   228  		log: `
   229  		    running task t1`,
   230  
   231  		state: `
   232  			v0 (SUCCESS):
   233  			    task:    t1: SUCCESS`,
   234  	}, {
   235  		name: "node with two tasks",
   236  		init: func() {
   237  			v0 := node(nil)
   238  			success("t1", v0, c1AllAncestorsProcessed, 0)
   239  			success("t2", v0, c2ArcTypeKnown, 0)
   240  		},
   241  		log: `
   242  		    running task t1
   243  		    running task t2`,
   244  
   245  		state: `
   246  			v0 (SUCCESS):
   247  			    task:    t1: SUCCESS
   248  			    task:    t2: SUCCESS`,
   249  	}, {
   250  		name: "node failing task",
   251  		init: func() {
   252  			v0 := node(nil)
   253  			fail("t1", v0, c1AllAncestorsProcessed, 0)
   254  		},
   255  		log: `
   256  		    running task t1: FAIL`,
   257  
   258  		state: `
   259  			v0 (SUCCESS):
   260  			    task:    t1: FAILED`,
   261  	}, {
   262  		// Tasks will have to be run in order according to their dependencies.
   263  		// Note that the tasks will be run in order, as they all depend on the
   264  		// same node, in which case the order must be and will be strictly
   265  		// enforced.
   266  		name: "dependency chain on nodes within scheduler",
   267  		init: func() {
   268  			v0 := node(nil)
   269  			success("third", v0, c3ValueKnown, c2ArcTypeKnown)
   270  			success("fourth", v0, c4ScalarKnown, c3ValueKnown)
   271  			success("second", v0, c2ArcTypeKnown, c1AllAncestorsProcessed)
   272  			success("first", v0, c1AllAncestorsProcessed, 0)
   273  		},
   274  		log: `
   275  		    running task first
   276  		    running task second
   277  		    running task third
   278  		    running task fourth`,
   279  
   280  		state: `
   281  			v0 (SUCCESS):
   282  			    task:    third: SUCCESS
   283  			    task:    fourth: SUCCESS
   284  			    task:    second: SUCCESS
   285  			    task:    first: SUCCESS`,
   286  	}, {
   287  		// If a task depends on a state completion for which there is no task,
   288  		// it should be considered as completed, because essentially all
   289  		// information is known about that state.
   290  		name: "task depends on state for which there is no task",
   291  		init: func() {
   292  			v0 := node(nil)
   293  			success("t1", v0, c2ArcTypeKnown, c1AllAncestorsProcessed)
   294  		},
   295  		log: `
   296  		    running task t1`,
   297  		state: `
   298  			v0 (SUCCESS):
   299  			    task:    t1: SUCCESS`,
   300  	}, {
   301  		// Same as previous, but now for another node.
   302  		name: "task depends on state of other node for which there is no task",
   303  		init: func() {
   304  			v0 := node(nil)
   305  			v1 := node(v0)
   306  			v2 := node(v0)
   307  			success("t1", v1, c1AllAncestorsProcessed, 0, dep{node: v2, needs: c2ArcTypeKnown})
   308  		},
   309  		log: `
   310  		    running task t1`,
   311  		state: `
   312  			v0 (SUCCESS):
   313  			v1 (SUCCESS):
   314  			    task:    t1: SUCCESS
   315  			v2 (SUCCESS):`,
   316  	}, {
   317  		name: "tasks depend on multiple other tasks within same scheduler",
   318  		init: func() {
   319  			v0 := node(nil)
   320  			success("before1", v0, c2ArcTypeKnown, 0)
   321  			success("last", v0, c4ScalarKnown, c1AllAncestorsProcessed|c2ArcTypeKnown|c3ValueKnown)
   322  			success("block", v0, c3ValueKnown, c1AllAncestorsProcessed|c2ArcTypeKnown)
   323  			success("before2", v0, c1AllAncestorsProcessed, 0)
   324  		},
   325  		log: `
   326  		    running task before1
   327  		    running task before2
   328  		    running task block
   329  		    running task last`,
   330  
   331  		state: `
   332  			v0 (SUCCESS):
   333  			    task:    before1: SUCCESS
   334  			    task:    last: SUCCESS
   335  			    task:    block: SUCCESS
   336  			    task:    before2: SUCCESS`,
   337  	}, {
   338  		// In this test we simulate dynamic reference that are dependent
   339  		// on each other in a chain to form the fields. Task t0 would not be
   340  		// a task in the regular evaluator, but it is included there as a
   341  		// task in absence of the ability to simulate static elements.
   342  		//
   343  		//	v0: {
   344  		//		(v0.baz): "bar" // task t1
   345  		//		(v0.foo): "baz" // task t2
   346  		//		baz: "foo"      // task t0
   347  		//	}
   348  		//
   349  		name: "non-cyclic dependencies between nodes p1",
   350  		init: func() {
   351  			v0 := node(nil)
   352  			baz := node(v0)
   353  			success("t0", baz, c1AllAncestorsProcessed, 0)
   354  			foo := node(v0)
   355  
   356  			completes("t1:bar", v0, foo, c2ArcTypeKnown, dep{node: baz, needs: c1AllAncestorsProcessed})
   357  			success("t2:baz", v0, c1AllAncestorsProcessed, 0, dep{node: foo, needs: c2ArcTypeKnown})
   358  		},
   359  		log: `
   360  		    running task t1:bar
   361  		    running task t0
   362  		    running task t2:baz`,
   363  		state: `
   364  			v0 (SUCCESS):
   365  			    task:    t1:bar: SUCCESS
   366  			    task:    t2:baz: SUCCESS
   367  			v1 (SUCCESS):
   368  			    task:    t0: SUCCESS
   369  			v2 (SUCCESS):`,
   370  	}, {
   371  		// Like the previous test, but different order of execution.
   372  		//
   373  		//	v0: {
   374  		//		(v0.foo): "baz" // task t2
   375  		//		(v0.baz): "bar" // task t1
   376  		//		baz: "foo"      // task t0
   377  		//	}
   378  		//
   379  		name: "non-cyclic dependencies between nodes p2",
   380  		init: func() {
   381  			v0 := node(nil)
   382  			baz := node(v0)
   383  			success("foo", baz, c1AllAncestorsProcessed, 0)
   384  			foo := node(v0)
   385  
   386  			success("t2:baz", v0, c1AllAncestorsProcessed, 0, dep{node: foo, needs: c2ArcTypeKnown})
   387  			completes("t1:bar", v0, foo, c2ArcTypeKnown, dep{node: baz, needs: c1AllAncestorsProcessed})
   388  		},
   389  		log: `
   390  		    running task t2:baz
   391  		    running task t1:bar
   392  		    running task foo`,
   393  		state: `
   394  			v0 (SUCCESS):
   395  			    task:    t2:baz: SUCCESS
   396  			    task:    t1:bar: SUCCESS
   397  			v1 (SUCCESS):
   398  			    task:    foo: SUCCESS
   399  			v2 (SUCCESS):`,
   400  	}, {
   401  		//	    b: a - 10
   402  		//	    a: b + 10
   403  		name: "cycle in mutually referencing expressions",
   404  		init: func() {
   405  			v0 := node(nil)
   406  			v1 := node(v0)
   407  			v2 := node(v0)
   408  			success("a-10", v1, c1AllAncestorsProcessed|c2ArcTypeKnown, 0, dep{node: v2, needs: c1AllAncestorsProcessed})
   409  			success("b+10", v2, c1AllAncestorsProcessed|c2ArcTypeKnown, 0, dep{node: v1, needs: c1AllAncestorsProcessed})
   410  		},
   411  		log: `
   412  		    running task a-10
   413  		    running task b+10
   414  		        task b+10 waiting for v1 meeting 1: BLOCKED
   415  		        task a-10 waiting for v2 meeting 1: BLOCKED
   416  		    running task b+10
   417  		    running task a-10`,
   418  		state: `
   419  			v0 (SUCCESS):
   420  			v1 (SUCCESS): (frozen)
   421  			    task:    a-10: SUCCESS (unblocked)
   422  			v2 (SUCCESS): (frozen)
   423  			    task:    b+10: SUCCESS (unblocked)`,
   424  	}, {
   425  		//	    b: a - 10
   426  		//	    a: b + 10
   427  		//	    a: 5
   428  		name: "broken cyclic reference in expressions",
   429  		init: func() {
   430  			v0 := node(nil)
   431  			v1 := node(v0)
   432  			v2 := node(v0)
   433  			success("a-10", v1, c1AllAncestorsProcessed|c2ArcTypeKnown, 0, dep{node: v2, needs: c1AllAncestorsProcessed})
   434  			success("b+10", v2, c1AllAncestorsProcessed|c2ArcTypeKnown, 0, dep{node: v1, needs: c1AllAncestorsProcessed})
   435  
   436  			// NOTE: using success("5", v2, c1, 0) here would cause the cyclic
   437  			// references to block, as they would both provide and depend on
   438  			// v1 and v2 becoming scalars. Once a field is known to be a scalar,
   439  			// it can safely be signaled as unification cannot make it more
   440  			// concrete. Further unification could result in an error, but that
   441  			// will be caught by completing the unification.
   442  			signal("5", v2, c1AllAncestorsProcessed)
   443  		},
   444  		log: `
   445  		    running task a-10
   446  		    running task b+10
   447  		        task b+10 waiting for v1 meeting 1: BLOCKED
   448  		    running task 5
   449  		    running task b+10`,
   450  		state: `
   451  			v0 (SUCCESS):
   452  			v1 (SUCCESS):
   453  			    task:    a-10: SUCCESS
   454  			v2 (SUCCESS):
   455  			    task:    b+10: SUCCESS
   456  			    task:    5: SUCCESS`,
   457  	}, {
   458  		// This test simulates a case where a comprehension projects
   459  		// onto itself. The cycle is broken by allowing a required state
   460  		// to be dropped upon detecting a cycle. For comprehensions,
   461  		// for instance, one usually would define that it provides fields in
   462  		// the vertex in which it is defined. However, for self-projections
   463  		// this results in a cycle. By dropping the requirement that all fields
   464  		// need be specified the cycle is broken. However, this means the
   465  		// comprehension may no longer add new fields to the vertex.
   466  		//
   467  		//	x: {
   468  		//		for k, v in x {
   469  		//			(k): v
   470  		//		}
   471  		//		foo: 5
   472  		//	}
   473  		name: "self cyclic",
   474  		init: func() {
   475  			x := node(nil)
   476  			foo := node(x)
   477  			success("5", foo, c1AllAncestorsProcessed, 0)
   478  			success("comprehension", x, c1AllAncestorsProcessed, 0, dep{node: x, needs: c1AllAncestorsProcessed})
   479  		},
   480  		log: `
   481  		    running task comprehension
   482  		        task comprehension waiting for v0 meeting 1: BLOCKED
   483  		    running task comprehension
   484  		    running task 5`,
   485  		state: `
   486  			v0 (SUCCESS): (frozen)
   487  			    task:    comprehension: SUCCESS (unblocked)
   488  			v1 (SUCCESS):
   489  			    task:    5: SUCCESS`,
   490  	}, {
   491  		// This test simulates a case where comprehensions are not allowed to
   492  		// project on themselves. CUE allows this, but it is to test that
   493  		// similar constructions where this is not allowed do not cause
   494  		// infinite loops.
   495  		//
   496  		//	x: {
   497  		//		for k, v in x {
   498  		//			(k+"X"): v
   499  		//		}
   500  		//		foo: 5
   501  		//	}
   502  		// TODO: override freeze.
   503  		name: "self cyclic not allowed",
   504  		init: func() {
   505  			x := node(nil)
   506  			foo := node(x)
   507  			success("5", foo, c1AllAncestorsProcessed, 0)
   508  			success("comprehension", x, c1AllAncestorsProcessed, 0, dep{node: x, needs: c1AllAncestorsProcessed})
   509  		},
   510  		log: `
   511  		    running task comprehension
   512  		        task comprehension waiting for v0 meeting 1: BLOCKED
   513  		    running task comprehension
   514  		    running task 5`,
   515  		state: `
   516  			v0 (SUCCESS): (frozen)
   517  			    task:    comprehension: SUCCESS (unblocked)
   518  			v1 (SUCCESS):
   519  			    task:    5: SUCCESS`,
   520  	}, {
   521  		// This test simulates a case where comprehensions mutually project
   522  		// on each other.
   523  		//
   524  		//	x: {
   525  		//		for k, v in y {
   526  		//			(k): v
   527  		//		}
   528  		//	}
   529  		//	y: {
   530  		//		for k, v in x {
   531  		//			(k): v
   532  		//		}
   533  		//	}
   534  		name: "mutually cyclic projection",
   535  		init: func() {
   536  			v0 := node(nil)
   537  			x := node(v0)
   538  			y := node(v0)
   539  
   540  			success("comprehension", x, c1AllAncestorsProcessed, 0, dep{node: y, needs: c1AllAncestorsProcessed})
   541  			success("comprehension", y, c1AllAncestorsProcessed, 0, dep{node: x, needs: c1AllAncestorsProcessed})
   542  
   543  		},
   544  		log: `
   545  		    running task comprehension
   546  		    running task comprehension
   547  		        task comprehension waiting for v1 meeting 1: BLOCKED
   548  		        task comprehension waiting for v2 meeting 1: BLOCKED
   549  		    running task comprehension
   550  		    running task comprehension`,
   551  		state: `
   552  			v0 (SUCCESS):
   553  			v1 (SUCCESS): (frozen)
   554  			    task:    comprehension: SUCCESS (unblocked)
   555  			v2 (SUCCESS): (frozen)
   556  			    task:    comprehension: SUCCESS (unblocked)`,
   557  	}, {
   558  		// This test simulates a case where comprehensions are not allowed to
   559  		// project on each other cyclicly. CUE allows this, but it is to test
   560  		// that similar constructions where this is not allowed do not cause
   561  		// infinite loops.
   562  		//
   563  		//	x: {
   564  		//		for k, v in y {
   565  		//			(k): v
   566  		//		}
   567  		//	}
   568  		//	y: {
   569  		//		for k, v in x {
   570  		//			(k): v
   571  		//		}
   572  		//		foo: 5
   573  		//	}
   574  		name: "disallowed mutually cyclic projection",
   575  		init: func() {
   576  			v0 := node(nil)
   577  			x := node(v0)
   578  			y := node(v0)
   579  			foo := node(y)
   580  			success("5", foo, c1AllAncestorsProcessed, 0)
   581  
   582  			success("comprehension", x, c1AllAncestorsProcessed, 0, dep{node: y, needs: c1AllAncestorsProcessed})
   583  			success("comprehension", y, c1AllAncestorsProcessed, 0, dep{node: x, needs: c1AllAncestorsProcessed})
   584  
   585  		},
   586  		log: `
   587  		    running task comprehension
   588  		    running task comprehension
   589  		        task comprehension waiting for v1 meeting 1: BLOCKED
   590  		        task comprehension waiting for v2 meeting 1: BLOCKED
   591  		    running task comprehension
   592  		    running task comprehension
   593  		    running task 5`,
   594  		state: `
   595  			v0 (SUCCESS):
   596  			v1 (SUCCESS): (frozen)
   597  			    task:    comprehension: SUCCESS (unblocked)
   598  			v2 (SUCCESS): (frozen)
   599  			    task:    comprehension: SUCCESS (unblocked)
   600  			v3 (SUCCESS):
   601  			    task:    5: SUCCESS`,
   602  	}}
   603  
   604  	cuetest.Run(t, cases, func(t *cuetest.T, tc *testCase) {
   605  		// t.Update(true)
   606  		// t.Select("non-cyclic_dependencies_between_nodes_p2")
   607  
   608  		nodeID = 0
   609  		nodes = nodes[:0]
   610  		w.Reset()
   611  
   612  		// Create and run root scheduler.
   613  		tc.init()
   614  		for _, n := range nodes {
   615  			n.provided |= autoFieldConjunctsKnown
   616  			n.signalDoneAdding()
   617  		}
   618  		for _, n := range nodes {
   619  			n.finalize(autoFieldConjunctsKnown)
   620  		}
   621  
   622  		t.Equal(w.String(), tc.log)
   623  
   624  		w := &strings.Builder{}
   625  		for _, n := range nodes {
   626  			fmt.Fprintf(w, "\n\t\t\tv%d (%v):", n.refCount, n.state)
   627  			if n.scheduler.isFrozen {
   628  				fmt.Fprint(w, " (frozen)")
   629  			}
   630  			for _, t := range n.tasks {
   631  				fmt.Fprintf(w, "\n\t\t\t    task:    %s: %v", t.x.(*String).Str, t.state)
   632  				if t.unblocked {
   633  					fmt.Fprint(w, " (unblocked)")
   634  				}
   635  			}
   636  			for _, t := range n.blocking {
   637  				if t.blockedOn != nil {
   638  					fmt.Fprintf(w, "\n\t\t\t    blocked: %s: %v", t.x.(*String).Str, t.state)
   639  				}
   640  			}
   641  		}
   642  
   643  		t.Equal(w.String(), tc.state)
   644  	})
   645  }