@Test
public void testArrayAccessesAreInBounds() throws Exception {

  // This checks that array accesses are correctly made in-bounds when injecting live code.
  final ShaderJobFileOperations fileOps = new ShaderJobFileOperations();
  final File donors = testFolder.newFolder("donors");
  final File referenceFile = testFolder.newFile("reference.json");
  {
    // This donor is designed to have a high chance of leading to an array access getting injected
    // such that the array indexing expression will be a free variable for which a fuzzed initial
    // value will be created.
    final String donorSource =
        "#version 300 es\n"
            + "void main() {\n"
            + " int x = 0;\n"
            + " {\n"
            + "  int A[1];\n"
            + "  A[x] = 42;\n"
            + "  {\n"
            + "   int B[1];\n"
            + "   B[x] = 42;\n"
            + "   {\n"
            + "    int C[1];\n"
            + "    C[x] = 42;\n"
            + "   }\n"
            + "  }\n"
            + " }\n"
            + "}\n";
    fileOps.writeShaderJobFileFromImageJob(
        new ImageJob()
            .setFragmentSource(donorSource)
            .setUniformsInfo("{}"),
        new File(donors, "donor.json")
    );
  }
  {
    final String referenceSource = "#version 300 es\n"
        + "void main() {\n"
        + "}\n";
    fileOps.writeShaderJobFileFromImageJob(
        new ImageJob()
            .setFragmentSource(referenceSource)
            .setUniformsInfo("{}"),
        referenceFile
    );
  }
  int noCodeDonatedCount = 0;
  // Try the following a few times, so that there is a good chance of triggering the issue
  // this test was used to catch, should it return:
  for (int seed = 0; seed < 15; seed++) {
    final ShaderJob referenceShaderJob = fileOps.readShaderJobFile(referenceFile);
    // Do live code donation.
    DonateLiveCodeTransformation transformation =
        new DonateLiveCodeTransformation(IRandom::nextBoolean, donors,
            GenerationParams.normal(ShaderKind.FRAGMENT, true), false);
    assert referenceShaderJob.getFragmentShader().isPresent();
    boolean result = transformation.apply(
        referenceShaderJob.getFragmentShader().get(),
        TransformationProbabilities.onlyLiveCodeAlwaysSubstitute(),
        new RandomWrapper(seed),
        GenerationParams.normal(ShaderKind.FRAGMENT, true)
    );
    if (!result) {
      ++noCodeDonatedCount;
      continue;
    }
    // An array access injected into the shader must either be (1) already in bounds, or
    // (2) made in bounds.  Only in the former case can the array index be a variable identifier
    // expression, and in that case the expression cannot realistically be statically in bounds
    // if the initializer for that expression is under a _GLF_FUZZED macro.  (There is a tiny
    // chance that the fuzzed expression might statically evaluate to 0, but currently a
    // _GLF_FUZZED macro will be treated as not statically in bounds, so the access would be
    // made in bounds in that case.)
    //
    // The following thus checks that if an array is indexed directly by a variable reference,
    // the initializer for that variable is not a function call expression.
    new ScopeTrackingVisitor() {
      @Override
      public void visitArrayIndexExpr(ArrayIndexExpr arrayIndexExpr) {
        super.visitArrayIndexExpr(arrayIndexExpr);
        if (arrayIndexExpr.getIndex() instanceof VariableIdentifierExpr) {
          final ScopeEntry scopeEntry = getCurrentScope().lookupScopeEntry(
              ((VariableIdentifierExpr) arrayIndexExpr.getIndex()).getName());
          assertTrue(scopeEntry.hasVariableDeclInfo());
          assertNotNull(scopeEntry.getVariableDeclInfo().getInitializer());
          assertFalse((scopeEntry.getVariableDeclInfo().getInitializer())
              .getExpr() instanceof FunctionCallExpr);
        }
      }
    }.visit(referenceShaderJob.getFragmentShader().get());
  }
  // The above code tests donation of live code, but there is still a chance that no code will
  // be donated. We assert that this happens < 10 times to ensure that we get some test
  // coverage, but this could fail due to bad luck.
  Assert.assertTrue(
      "Donation failure count should be < 10, " + noCodeDonatedCount,
      noCodeDonatedCount < 10
  );
}