// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import "dart:io";
import "package:expect/expect.dart";

// The sizes of these classes are co-prime multiples of the allocation unit to
// increase the likelihood that scavenging fails from fragmentation.

// header + 13 fields = 7 allocation units
class A {
  dynamic field1;
  dynamic field2;
  dynamic field3;
  dynamic field4;
  dynamic field5;
  dynamic field6;
  dynamic field7;
  dynamic field8;
  dynamic field9;
  dynamic field10;
  dynamic field11;
  dynamic field12;
  dynamic field13;
}

// header + 17 fields = 9 allocation units
class B {
  dynamic field1;
  dynamic field2;
  dynamic field3;
  dynamic field4;
  dynamic field5;
  dynamic field6;
  dynamic field7;
  dynamic field8;
  dynamic field9;
  dynamic field10;
  dynamic field11;
  dynamic field12;
  dynamic field13;
  dynamic field14;
  dynamic field15;
  dynamic field16;
  dynamic field17;
}

// header + 19 fields = 10 allocation units
class C {
  dynamic field1;
  dynamic field2;
  dynamic field3;
  dynamic field4;
  dynamic field5;
  dynamic field6;
  dynamic field7;
  dynamic field8;
  dynamic field9;
  dynamic field10;
  dynamic field11;
  dynamic field12;
  dynamic field13;
  dynamic field14;
  dynamic field15;
  dynamic field16;
  dynamic field17;
  dynamic field18;
  dynamic field19;
}

makeA(n) {
  var a = new A();
  if (n > 0) {
    a.field1 = makeB(n - 1);
    a.field2 = makeC(n - 1);
    a.field3 = makeB(n - 1);
    a.field4 = makeC(n - 1);
  }
  return a;
}

makeB(n) {
  var b = new B();
  if (n > 0) {
    b.field1 = makeC(n - 1);
    b.field2 = makeA(n - 1);
    b.field3 = makeC(n - 1);
    b.field4 = makeA(n - 1);
  }
  return b;
}

makeC(n) {
  var c = new C();
  if (n > 0) {
    c.field1 = makeA(n - 1);
    c.field2 = makeB(n - 1);
    c.field3 = makeA(n - 1);
    c.field4 = makeB(n - 1);
  }
  return c;
}

readFields(x) {
  print(x.field1);
  print(x.field2);
  print(x.field3);
  print(x.field4);
}

main(List<String> argsIn) async {
  if (argsIn.contains("--testee")) {
    // Trigger OOM.
    // Must read the fields to prevent the writes from being optimized away. If
    // the writes are optimized away, most of the tree is collectible right away
    // and we timeout instead of triggering OOM.
    readFields(makeA(50));
    readFields(makeB(50));
    readFields(makeC(50));
    return;
  }

  var exec = Platform.executable;
  var args =
      Platform.executableArguments +
      [
        "--old_gen_heap_size=15" /*MB*/,
        "--verbose_gc",
        "--verify_after_gc",
        "--verify_store_buffer",
        Platform.script.toFilePath(),
        "--testee",
      ];
  print("+ $exec ${args.join(' ')}");

  var result = await Process.run(exec, args);
  print("Command stdout:");
  print(result.stdout);
  print("Command stderr:");
  print(result.stderr);

  Expect.equals(
    255,
    result.exitCode,
    "Should see runtime exception error code, not SEGV",
  );

  Expect.isTrue(
    result.stderr.contains("Unhandled exception:\nOut of Memory") ||
        result.stderr.contains("Unhandled exception:\r\nOut of Memory"),
    "Should see the Dart OutOfMemoryError",
  );

  Expect.isFalse(
    result.stderr.contains("error: Out of memory"),
    "Should not see the C++ OUT_OF_MEMORY()",
  );
}
