Repository: facebook/react
Pull request: Delete "triangle" resuming fuzz tester

This deletes the ReactIncrementalTriangle test suite, which I originally added back in 2017 when I was working on Fiber's "resuming" feature. It was meant to simulate a similar scenario as Seb's "Sierpinski Triangle" Fiber demo.

We eventually ended up removing resuming, but we kept this fuzz tester around since it wasn't really harming anything. However, over the years, we've had to make many small tweaks to decouple it from implementation details, to the point that it doesn't test anything useful anymore. And the thing that it originally tested has long since been removed.

If or when we do add back resuming, we would write a different fuzz tester from scratch rather than build on this one.

So rather than continue to contrive ways to prevent it from breaking, I propose we delete it.

We still have other fuzz testers for things like Suspense and context propagation. Only this particular one has outlived its usefulness.

Changed files:
filename: packages/react-reconciler/src/__tests__/ReactIncrementalTriangle-test.js
patch: @@ -1,592 +0,0 @@
-/**
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @emails react-core
- * @jest-environment node
- */
-
-'use strict';
-
-let React;
-let ReactNoop;
-let Scheduler;
-
-describe('ReactIncrementalTriangle', () => {
-  beforeEach(() => {
-    jest.resetModules();
-
-    React = require('react');
-    ReactNoop = require('react-noop-renderer');
-    Scheduler = require('scheduler');
-  });
-
-  function span(prop) {
-    return {type: 'span', children: [], prop, hidden: false};
-  }
-
-  const FLUSH = 'FLUSH';
-  function flush(unitsOfWork = Infinity) {
-    return {
-      type: FLUSH,
-      unitsOfWork,
-    };
-  }
-
-  const FLUSH_ALL = 'FLUSH_ALL';
-  function flushAll() {
-    return {
-      type: FLUSH_ALL,
-    };
-  }
-
-  const STEP = 'STEP';
-  function step(counter) {
-    return {
-      type: STEP,
-      counter,
-    };
-  }
-
-  const INTERRUPT = 'INTERRUPT';
-  function interrupt(key) {
-    return {
-      type: INTERRUPT,
-    };
-  }
-
-  const TOGGLE = 'TOGGLE';
-  function toggle(childIndex) {
-    return {
-      type: TOGGLE,
-      childIndex,
-    };
-  }
-
-  const EXPIRE = 'EXPIRE';
-  function expire(ms) {
-    return {
-      type: EXPIRE,
-      ms,
-    };
-  }
-
-  const STOP = 'STOP';
-
-  function randomInteger(min, max) {
-    min = Math.ceil(min);
-    max = Math.floor(max);
-    return Math.floor(Math.random() * (max - min)) + min;
-  }
-
-  function formatAction(action) {
-    switch (action.type) {
-      case FLUSH:
-        return `flush(${action.unitsOfWork})`;
-      case FLUSH_ALL:
-        return 'flushAll()';
-      case STEP:
-        return `step(${action.counter})`;
-      case INTERRUPT:
-        return 'interrupt()';
-      case TOGGLE:
-        return `toggle(${action.childIndex})`;
-      case EXPIRE:
-        return `expire(${action.ms})`;
-      default:
-        throw new Error('Switch statement should be exhaustive');
-    }
-  }
-
-  function formatActions(actions) {
-    let result = 'simulate(';
-    for (let i = 0; i < actions.length; i++) {
-      const action = actions[i];
-      result += formatAction(action);
-      if (i !== actions.length - 1) {
-        result += ', ';
-      }
-    }
-    result += ')';
-    return result;
-  }
-
-  const MAX_DEPTH = 3;
-  const TOTAL_CHILDREN = Math.pow(3, MAX_DEPTH);
-  let TOTAL_TRIANGLES = 0;
-  for (let i = 0; i <= MAX_DEPTH; i++) {
-    TOTAL_TRIANGLES += Math.pow(3, i);
-  }
-
-  function randomAction() {
-    const weights = [
-      [FLUSH, 1],
-      [FLUSH_ALL, 1],
-      [STEP, 1],
-      [INTERRUPT, 1],
-      [TOGGLE, 1],
-      [EXPIRE, 1],
-    ];
-    let totalWeight = 0;
-    for (let i = 0; i < weights.length; i++) {
-      totalWeight += weights[i][1];
-    }
-
-    const randomNumber = Math.random() * totalWeight;
-    let actionType;
-    let remainingWeight = randomNumber;
-    for (let i = 0; i < weights.length; i++) {
-      const [option, weight] = weights[i];
-      remainingWeight -= weight;
-      if (remainingWeight <= 0) {
-        actionType = option;
-        break;
-      }
-    }
-
-    switch (actionType) {
-      case FLUSH:
-        return flush(randomInteger(0, TOTAL_TRIANGLES * 1.5));
-      case FLUSH_ALL:
-        return flushAll();
-      case STEP:
-        return step(randomInteger(0, 10));
-      case INTERRUPT:
-        return interrupt();
-      case TOGGLE:
-        return toggle(randomInteger(0, TOTAL_TRIANGLES));
-      case EXPIRE:
-        return expire(randomInteger(0, 1500));
-      default:
-        throw new Error('Switch statement should be exhaustive');
-    }
-  }
-
-  function randomActions(n) {
-    const actions = [];
-    for (let i = 0; i < n; i++) {
-      actions.push(randomAction());
-    }
-    return actions;
-  }
-
-  function TriangleSimulator(rootID) {
-    const CounterContext = React.createContext([]);
-    const ActiveContext = React.createContext(0);
-
-    let triangles = [];
-    let leafTriangles = [];
-    const yieldAfterEachRender = false;
-    class Triangle extends React.Component {
-      constructor(props) {
-        super();
-        this.state = {isActive: false};
-        this.child = React.createRef(null);
-      }
-      activate() {
-        this.setState({isActive: true});
-      }
-      deactivate() {
-        this.setState({isActive: false});
-      }
-      shouldComponentUpdate(nextProps, nextState) {
-        return (
-          this.props.counter !== nextProps.counter ||
-          this.props.activeDepth !== nextProps.activeDepth ||
-          this.state.isActive !== nextState.isActive
-        );
-      }
-      componentDidMount() {
-        this.index = triangles.length;
-        triangles.push(this);
-        if (this.props.remainingDepth === 0) {
-          this.leafIndex = leafTriangles.length;
-          leafTriangles.push(this);
-        }
-      }
-      componentDidUpdate() {
-        if (this.child.current !== null) {
-          const {prop: currentCounter} = JSON.parse(this.child.current.prop);
-          if (this.props.counter !== currentCounter) {
-            throw new Error('Incorrect props in lifecycle');
-          }
-        }
-      }
-      render() {
-        if (yieldAfterEachRender) {
-          Scheduler.log(this);
-        }
-        const {counter, remainingDepth} = this.props;
-        return (
-          <ActiveContext.Consumer>
-            {activeContext => (
-              <CounterContext.Consumer>
-                {counterContext => {
-                  const activeDepthProp = this.state.isActive
-                    ? this.props.activeDepth + 1
-                    : this.props.activeDepth;
-                  const activeDepthContext = this.state.isActive
-                    ? activeContext + 1
-                    : activeContext;
-                  if (remainingDepth === 0) {
-                    // Leaf
-                    const output = JSON.stringify({
-                      prop: counter,
-                      isActive: this.state.isActive,
-                      counterContext: counterContext,
-                      activeDepthProp,
-                      activeDepthContext,
-                    });
-                    return <span ref={this.child} prop={output} />;
-                  }
-
-                  return (
-                    <ActiveContext.Provider value={activeDepthContext}>
-                      <CounterContext.Provider value={counter}>
-                        <Triangle
-                          counter={counter}
-                          activeDepth={activeDepthProp}
-                          remainingDepth={remainingDepth - 1}
-                        />
-                        <Triangle
-                          counter={counter}
-                          activeDepth={activeDepthProp}
-                          remainingDepth={remainingDepth - 1}
-                        />
-                        <Triangle
-                          counter={counter}
-                          activeDepth={activeDepthProp}
-                          remainingDepth={remainingDepth - 1}
-                        />
-                      </CounterContext.Provider>
-                    </ActiveContext.Provider>
-                  );
-                }}
-              </CounterContext.Consumer>
-            )}
-          </ActiveContext.Consumer>
-        );
-      }
-    }
-
-    let appInstance;
-    class App extends React.Component {
-      state = {counter: 0};
-      interrupt() {
-        // Triggers a restart from the top.
-        this.forceUpdate();
-      }
-      setCounter(counter) {
-        const currentCounter = this.state.counter;
-        this.setState({counter});
-        return currentCounter;
-      }
-      render() {
-        appInstance = this;
-        return (
-          <Triangle
-            counter={this.state.counter}
-            activeDepth={0}
-            remainingDepth={this.props.remainingDepth}
-          />
-        );
-      }
-    }
-
-    let keyCounter = 0;
-    function reset(nextStep = 0) {
-      triangles = [];
-      leafTriangles = [];
-      // Remounts the whole tree by changing the key
-      if (rootID) {
-        ReactNoop.renderToRootWithID(
-          <App remainingDepth={MAX_DEPTH} key={keyCounter++} />,
-          rootID,
-        );
-      } else {
-        ReactNoop.render(<App remainingDepth={MAX_DEPTH} key={keyCounter++} />);
-      }
-      Scheduler.unstable_flushAllWithoutAsserting();
-      assertConsistentTree();
-      return appInstance;
-    }
-
-    reset();
-
-    function assertConsistentTree(activeTriangleIndices = new Set(), counter) {
-      const childrenJSX = ReactNoop.getPendingChildrenAsJSX(rootID);
-      const children = childrenJSX === null ? [] : childrenJSX.props.children;
-
-      if (children.length !== TOTAL_CHILDREN) {
-        throw new Error('Wrong number of children.');
-      }
-
-      let expectedCounter = counter;
-
-      for (let i = 0; i < children.length; i++) {
-        const child = children[i];
-
-        const output = JSON.parse(child.props.prop);
-        const prop = output.prop;
-        const isActive = output.isActive;
-        const counterContext = output.counterContext;
-        const activeDepthProp = output.activeDepthProp;
-        const activeDepthContext = output.activeDepthContext;
-
-        // If an expected counter is not specified, use the value of the
-        // first child.
-        if (expectedCounter === undefined) {
-          expectedCounter = prop;
-        }
-        const expectedIsActive = activeTriangleIndices.has(i);
-
-        if (prop !== expectedCounter) {
-          throw new Error(
-            `Triangle ${i} is inconsistent: prop ${prop} instead of ` +
-              expectedCounter,
-          );
-        }
-
-        if (isActive !== expectedIsActive) {
-          throw new Error(
-            `Triangle ${i} is inconsistent: isActive ${isActive} instead of ` +
-              expectedIsActive,
-          );
-        }
-
-        if (counterContext !== prop) {
-          throw new Error(
-            `Triangle ${i} is inconsistent: prop ${prop} does not match ` +
-              `counterContext ${counterContext}`,
-          );
-        }
-
-        if (activeDepthContext !== activeDepthProp) {
-          throw new Error(
-            `Triangle ${i} is inconsistent: activeDepthProp ` +
-              `${activeDepthProp} does not match activeDepthContext ` +
-              activeDepthContext,
-          );
-        }
-      }
-    }
-
-    function* simulateAndYield() {
-      const app = reset();
-      let expectedCounterAtEnd = app.state.counter;
-
-      const activeIndices = new Set();
-      const activeLeafIndices = new Set();
-      let action;
-      while (true) {
-        action = yield;
-        if (action === STOP) {
-          break;
-        }
-        ReactNoop.flushSync(() => {
-          switch (action.type) {
-            case FLUSH:
-              Scheduler.unstable_flushNumberOfYields(action.unitsOfWork);
-              break;
-            case FLUSH_ALL:
-              Scheduler.unstable_flushAllWithoutAsserting();
-              break;
-            case STEP:
-              ReactNoop.deferredUpdates(() => {
-                app.setCounter(action.counter);
-                expectedCounterAtEnd = action.counter;
-              });
-              break;
-            case INTERRUPT:
-              app.interrupt();
-              break;
-            case TOGGLE:
-              const targetTriangle = triangles[action.childIndex];
-              if (targetTriangle === undefined) {
-                throw new Error('Target index is out of bounds');
-              }
-              const index = targetTriangle.index;
-              const leafIndex = targetTriangle.leafIndex;
-              if (activeIndices.has(index)) {
-                activeIndices.delete(index);
-                if (leafIndex !== undefined) {
-                  activeLeafIndices.delete(leafIndex);
-                }
-                targetTriangle.deactivate();
-              } else {
-                activeIndices.add(index);
-                if (leafIndex !== undefined) {
-                  activeLeafIndices.add(leafIndex);
-                }
-                targetTriangle.activate();
-              }
-              break;
-            case EXPIRE:
-              ReactNoop.expire(action.ms);
-              break;
-            default:
-              throw new Error('Switch statement should be exhaustive');
-          }
-        });
-        assertConsistentTree(activeLeafIndices);
-      }
-      // Flush remaining work
-      Scheduler.unstable_flushAllWithoutAsserting();
-      ReactNoop.flushSync();
-      assertConsistentTree(activeLeafIndices, expectedCounterAtEnd);
-    }
-
-    function simulate(...actions) {
-      const gen = simulateAndYield();
-      // Call this once to prepare the generator
-      gen.next();
-      // eslint-disable-next-line no-for-of-loops/no-for-of-loops
-      for (const action of actions) {
-        gen.next(action);
-      }
-      gen.next(STOP);
-    }
-
-    return {
-      simulateAndYield,
-      simulate,
-      randomAction,
-      randomActions,
-    };
-  }
-
-  describe('single root', () => {
-    // These tests are not deterministic because the inputs are randomized. It
-    // runs a limited number of tests on every run. If it fails, it will output
-    // the case that led to the failure. Add the failing case to the test above
-    // to prevent future regressions.
-    it('hard-coded tests', () => {
-      const {simulate} = TriangleSimulator();
-      simulate(step(1));
-      simulate(toggle(0), step(1), toggle(0));
-      simulate(step(1), toggle(0), flush(2), step(2), toggle(0));
-      simulate(step(1), flush(3), toggle(0), step(0));
-      simulate(step(1), flush(3), toggle(18), step(0));
-      simulate(step(4), flush(52), expire(1476), flush(17), step(0));
-      simulate(interrupt(), toggle(10), step(2), expire(990), flush(46));
-      simulate(interrupt(), step(6), step(7), toggle(6), interrupt());
-      simulate(interrupt(), toggle(31), toggle(31), toggle(13), step(1));
-    });
-
-    it('generative tests', () => {
-      const {simulate} = TriangleSimulator();
-
-      const limit = 1000;
-
-      for (let i = 0; i < limit; i++) {
-        const actions = randomActions(5);
-        try {
-          simulate(...actions);
-        } catch (e) {
-          console.error(
-            `Triangle fuzz tester error! Copy and paste the following line into the test suite:
-${formatActions(actions)}
-          `,
-          );
-          throw e;
-        }
-      }
-    });
-  });
-
-  describe('multiple roots', () => {
-    const rootIDs = ['a', 'b', 'c'];
-
-    function randomActionsPerRoot() {
-      function randomRootID() {
-        const index = randomInteger(0, rootIDs.length);
-        return rootIDs[index];
-      }
-
-      const actions = [];
-      for (let i = 0; i < 10; i++) {
-        const rootID = randomRootID();
-        const action = randomAction();
-        actions.push([rootID, action]);
-      }
-      return actions;
-    }
-
-    function formatActionsPerRoot(actions) {
-      let result = 'simulateMultipleRoots(';
-      for (let i = 0; i < actions.length; i++) {
-        const [rootID, action] = actions[i];
-        result += `['${rootID}', ${formatAction(action)}]`;
-        if (i !== actions.length - 1) {
-          result += ', ';
-        }
-      }
-      result += ')';
-      return result;
-    }
-
-    function simulateMultipleRoots(...actions) {
-      const roots = new Map();
-      // eslint-disable-next-line no-for-of-loops/no-for-of-loops
-      for (const rootID of rootIDs) {
-        const simulator = TriangleSimulator(rootID);
-        const generator = simulator.simulateAndYield();
-        // Call this once to prepare the generator
-        generator.next();
-        roots.set(rootID, generator);
-      }
-
-      actions.forEach(([rootID, action]) => {
-        const generator = roots.get(rootID);
-        generator.next(action);
-      });
-      roots.forEach(generator => {
-        generator.next(STOP);
-      });
-    }
-
-    it('hard-coded tests', () => {
-      simulateMultipleRoots(
-        ['b', interrupt()],
-        ['a', toggle(22)],
-        ['c', step(4)],
-        ['a', expire(10)],
-        ['a', interrupt()],
-        ['c', step(2)],
-        ['b', interrupt()],
-      );
-
-      simulateMultipleRoots(
-        ['c', toggle(0)],
-        ['c', step(1)],
-        ['b', flush(7)],
-        ['c', toggle(0)],
-      );
-
-      simulateMultipleRoots(
-        ['c', step(1)],
-        ['c', expire(5000)],
-        ['b', toggle(1)],
-      );
-    });
-
-    it('generative tests', () => {
-      const limit = 100;
-      for (let i = 0; i < limit; i++) {
-        const actions = randomActionsPerRoot();
-        try {
-          simulateMultipleRoots(...actions);
-        } catch (e) {
-          console.error(
-            `Triangle fuzz tester error! Copy and paste the following line into the test suite:
-${formatActionsPerRoot(actions)}
-              `,
-          );
-          throw e;
-        }
-      }
-    });
-  });
-});