// Copyright (c) 2015, 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.

#include "vm/source_report.h"
#include "vm/dart_api_impl.h"
#include "vm/unit_test.h"

namespace dart {

#ifndef PRODUCT

static ObjectPtr ExecuteScript(const char* script) {
  Dart_Handle lib;
  {
    TransitionVMToNative transition(Thread::Current());
    lib = TestCase::LoadTestScript(script, nullptr);
    EXPECT_VALID(lib);
    Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, nullptr);
    EXPECT_VALID(result);
  }
  return Api::UnwrapHandle(lib);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_NoCalls) {
  // WARNING: This MUST be big enough for the serialized JSON string.
  const int kBufferSize = 1024;
  char buffer[kBufferSize];
  const char* kScript =
      "main() {\n"
      "}";

  Library& lib = Library::Handle();
  lib ^= ExecuteScript(kScript);
  ASSERT(!lib.IsNull());
  const Script& script =
      Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));
  SourceReport report(SourceReport::kCoverage);
  JSONStream js;
  report.PrintJSON(&js, script);
  const char* json_str = js.ToCString();
  ASSERT(strlen(json_str) < kBufferSize);
  ElideJSONSubstring("libraries", json_str, buffer);
  EXPECT_STREQ(
      "{\"type\":\"SourceReport\",\"ranges\":"

      // One compiled range, one hit at function declaration.
      "[{\"scriptIndex\":0,\"startPos\":0,\"endPos\":9,\"compiled\":true,"
      "\"coverage\":{\"hits\":[0],\"misses\":[]}}],"

      // One script in the script table.
      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\","
      "\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
      buffer);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_Filters_single) {
  // WARNING: This MUST be big enough for the serialized JSON string.
  const int kBufferSize = 1024;
  char buffer[kBufferSize];
  const char* kScript =
      "main() {\n"
      "}";

  Library& lib = Library::Handle();
  lib ^= ExecuteScript(kScript);
  ASSERT(!lib.IsNull());

  GrowableObjectArray& filters =
      GrowableObjectArray::Handle(GrowableObjectArray::New());
  filters.Add(String::Handle(String::New(RESOLVED_USER_TEST_URI)));
  SourceReport report(SourceReport::kCoverage, filters);
  JSONStream js;
  report.PrintJSON(&js, Script::Handle(Script::null()));
  const char* json_str = js.ToCString();
  ASSERT(strlen(json_str) < kBufferSize);
  ElideJSONSubstring("libraries", json_str, buffer);
  EXPECT_STREQ(
      "{\"type\":\"SourceReport\",\"ranges\":"

      // One compiled range, one hit at function declaration.
      "[{\"scriptIndex\":0,\"startPos\":0,\"endPos\":9,\"compiled\":true,"
      "\"coverage\":{\"hits\":[0],\"misses\":[]}}],"

      // One script in the script table.
      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\","
      "\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
      buffer);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_Filters_empty) {
  // WARNING: This MUST be big enough for the serialized JSON string.
  const int kBufferSize = 1024;
  char buffer[kBufferSize];
  const char* kScript =
      "main() {\n"
      "}";

  Library& lib = Library::Handle();
  lib ^= ExecuteScript(kScript);
  ASSERT(!lib.IsNull());

  GrowableObjectArray& filters =
      GrowableObjectArray::Handle(GrowableObjectArray::New());
  filters.Add(String::Handle(String::New("foo:bar/")));
  SourceReport report(SourceReport::kCoverage, filters);
  JSONStream js;
  report.PrintJSON(&js, Script::Handle(Script::null()));
  const char* json_str = js.ToCString();
  ASSERT(strlen(json_str) < kBufferSize);
  ElideJSONSubstring("libraries", json_str, buffer);
  EXPECT_STREQ(
      "{\"type\":\"SourceReport\",\"ranges\":"

      // No compiled range.
      "[],"

      // No script.
      "\"scripts\":[]}",
      buffer);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_SimpleCall) {
  // WARNING: This MUST be big enough for the serialized JSON string.
  const int kBufferSize = 1024;
  char buffer[kBufferSize];
  const char* kScript =
      "helper0() {}\n"
      "helper1() {}\n"
      "main() {\n"
      "  if (true) {\n"
      "    helper0();\n"
      "  } else {\n"
      "    helper1();\n"
      "  }\n"
      "}";

  Library& lib = Library::Handle();
  lib ^= ExecuteScript(kScript);
  ASSERT(!lib.IsNull());
  const Script& script =
      Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));

  SourceReport report(SourceReport::kCoverage);
  JSONStream js;
  report.PrintJSON(&js, script);
  const char* json_str = js.ToCString();
  ASSERT(strlen(json_str) < kBufferSize);
  ElideJSONSubstring("classes", json_str, buffer);
  ElideJSONSubstring("libraries", buffer, buffer);
  EXPECT_STREQ(
      "{\"type\":\"SourceReport\",\"ranges\":["

      // One range compiled with one hit at function declaration (helper0).
      "{\"scriptIndex\":0,\"startPos\":0,\"endPos\":11,\"compiled\":true,"
      "\"coverage\":{\"hits\":[0],\"misses\":[]}},"

      // One range not compiled (helper1).
      "{\"scriptIndex\":0,\"startPos\":13,\"endPos\":24,\"compiled\":false},"

      // One range with two hits and a miss (main).
      "{\"scriptIndex\":0,\"startPos\":26,\"endPos\":94,\"compiled\":true,"
      "\"coverage\":{\"hits\":[26,53],\"misses\":[79]}}],"

      // Only one script in the script table.
      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\","
      "\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
      buffer);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_ForceCompile) {
  // WARNING: This MUST be big enough for the serialized JSON string.
  const int kBufferSize = 1024;
  char buffer[kBufferSize];
  const char* kScript =
      "helper0() {}\n"
      "helper1() {}\n"
      "main() {\n"
      "  if (true) {\n"
      "    helper0();\n"
      "  } else {\n"
      "    helper1();\n"
      "  }\n"
      "}";

  Library& lib = Library::Handle();
  lib ^= ExecuteScript(kScript);
  ASSERT(!lib.IsNull());
  const Script& script =
      Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));

  SourceReport report(SourceReport::kCoverage, SourceReport::kForceCompile);
  JSONStream js;
  report.PrintJSON(&js, script);
  const char* json_str = js.ToCString();
  ASSERT(strlen(json_str) < kBufferSize);
  ElideJSONSubstring("classes", json_str, buffer);
  ElideJSONSubstring("libraries", buffer, buffer);

  EXPECT_STREQ(
      "{\"type\":\"SourceReport\",\"ranges\":["

      // One range compiled with one hit at function declaration (helper0).
      "{\"scriptIndex\":0,\"startPos\":0,\"endPos\":11,\"compiled\":true,"
      "\"coverage\":{\"hits\":[0],\"misses\":[]}},"

      // This range is compiled even though it wasn't called (helper1).
      "{\"scriptIndex\":0,\"startPos\":13,\"endPos\":24,\"compiled\":true,"
      "\"coverage\":{\"hits\":[],\"misses\":[13]}},"

      // One range with two hits and a miss (main).
      "{\"scriptIndex\":0,\"startPos\":26,\"endPos\":94,\"compiled\":true,"
      "\"coverage\":{\"hits\":[26,53],\"misses\":[79]}}],"

      // Only one script in the script table.
      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\","
      "\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
      buffer);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_UnusedClass_NoForceCompile) {
  // WARNING: This MUST be big enough for the serialized JSON string.
  const int kBufferSize = 1024;
  char buffer[kBufferSize];
  const char* kScript =
      "helper0() {}\n"
      "class Unused {\n"
      "  helper1() { helper0(); }\n"
      "}\n"
      "main() {\n"
      "  helper0();\n"
      "}";

  Library& lib = Library::Handle();
  lib ^= ExecuteScript(kScript);
  ASSERT(!lib.IsNull());
  const Script& script =
      Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));

  SourceReport report(SourceReport::kCoverage);
  JSONStream js;
  report.PrintJSON(&js, script);
  const char* json_str = js.ToCString();
  ASSERT(strlen(json_str) < kBufferSize);
  ElideJSONSubstring("classes", json_str, buffer);
  ElideJSONSubstring("libraries", buffer, buffer);
  EXPECT_STREQ(
      "{\"type\":\"SourceReport\",\"ranges\":["

      // UnusedClass is not compiled.
      "{\"scriptIndex\":0,\"startPos\":13,\"endPos\":55,\"compiled\":false},"

      // helper0 is compiled.
      "{\"scriptIndex\":0,\"startPos\":0,\"endPos\":11,\"compiled\":true,"
      "\"coverage\":{\"hits\":[0],\"misses\":[]}},"

      // One range with two hits (main).
      "{\"scriptIndex\":0,\"startPos\":57,\"endPos\":79,\"compiled\":true,"
      "\"coverage\":{\"hits\":[57,68],\"misses\":[]}}],"

      // Only one script in the script table.
      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\","
      "\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
      buffer);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_UnusedClass_ForceCompile) {
  // WARNING: This MUST be big enough for the serialized JSON string.
  const int kBufferSize = 1024;
  char buffer[kBufferSize];
  const char* kScript =
      "helper0() {}\n"
      "class Unused {\n"
      "  helper1() { helper0(); }\n"
      "}\n"
      "main() {\n"
      "  helper0();\n"
      "}";

  Library& lib = Library::Handle();
  lib ^= ExecuteScript(kScript);
  ASSERT(!lib.IsNull());
  const Script& script =
      Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));

  SourceReport report(SourceReport::kCoverage, SourceReport::kForceCompile);
  JSONStream js;
  report.PrintJSON(&js, script);
  const char* json_str = js.ToCString();
  ASSERT(strlen(json_str) < kBufferSize);
  ElideJSONSubstring("classes", json_str, buffer);
  ElideJSONSubstring("libraries", buffer, buffer);
  EXPECT_STREQ(
      "{\"type\":\"SourceReport\",\"ranges\":["

      // UnusedClass.helper1 is compiled.
      "{\"scriptIndex\":0,\"startPos\":30,\"endPos\":53,\"compiled\":true,"
      "\"coverage\":{\"hits\":[],\"misses\":[30,42]}},"

      // helper0 is compiled.
      "{\"scriptIndex\":0,\"startPos\":0,\"endPos\":11,\"compiled\":true,"
      "\"coverage\":{\"hits\":[0],\"misses\":[]}},"

      // One range with two hits (main).
      "{\"scriptIndex\":0,\"startPos\":57,\"endPos\":79,\"compiled\":true,"
      "\"coverage\":{\"hits\":[57,68],\"misses\":[]}}],"

      // Only one script in the script table.
      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\","
      "\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
      buffer);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_LibrariesAlreadyCompiled) {
  // WARNING: This MUST be big enough for the serialized JSON string.
  const int kBufferSize = 1024;
  char buffer[kBufferSize];
  const char* kScript =
      "helper0() {}\n"
      "class Unused {\n"
      "  helper1() { helper0(); }\n"
      "}\n"
      "main() {\n"
      "  helper0();\n"
      "}";

  Library& lib = Library::Handle();
  lib ^= ExecuteScript(kScript);
  ASSERT(!lib.IsNull());
  const Script& script =
      Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));

  Zone* zone = thread->zone();
  ZoneCStringSet* libraries_already_compiled = new (zone) ZoneCStringSet(zone);
  libraries_already_compiled->Insert(RESOLVED_USER_TEST_URI);
  SourceReport report(SourceReport::kCoverage, GrowableObjectArray::Handle(),
                      libraries_already_compiled, SourceReport::kForceCompile);
  JSONStream js;
  report.PrintJSON(&js, script);
  const char* json_str = js.ToCString();
  ASSERT(strlen(json_str) < kBufferSize);
  ElideJSONSubstring("classes", json_str, buffer);
  ElideJSONSubstring("libraries", buffer, buffer);
  EXPECT_STREQ(
      "{\"type\":\"SourceReport\",\"ranges\":["

      // UnusedClass is not compiled.
      "{\"scriptIndex\":0,\"startPos\":13,\"endPos\":55,\"compiled\":false},"

      // helper0 is compiled.
      "{\"scriptIndex\":0,\"startPos\":0,\"endPos\":11,\"compiled\":true,"
      "\"coverage\":{\"hits\":[0],\"misses\":[]}},"

      // One range with two hits (main).
      "{\"scriptIndex\":0,\"startPos\":57,\"endPos\":79,\"compiled\":true,"
      "\"coverage\":{\"hits\":[57,68],\"misses\":[]}}],"

      // Only one script in the script table.
      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\","
      "\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
      buffer);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_NestedFunctions) {
  // WARNING: This MUST be big enough for the serialized JSON string.
  const int kBufferSize = 1024;
  char buffer[kBufferSize];
  const char* kScript =
      "helper0() {\n"
      "  nestedHelper0() {}\n"
      "  nestedHelper1() {}\n"
      "  nestedHelper0();\n"
      "}\n"
      "helper1() {}\n"
      "main() {\n"
      "  if (true) {\n"
      "    helper0();\n"
      "  } else {\n"
      "    helper1();\n"
      "  }\n"
      "}";

  Library& lib = Library::Handle();
  lib ^= ExecuteScript(kScript);
  ASSERT(!lib.IsNull());
  const Script& script =
      Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));

  SourceReport report(SourceReport::kCoverage);
  JSONStream js;
  report.PrintJSON(&js, script);
  const char* json_str = js.ToCString();
  ASSERT(strlen(json_str) < kBufferSize);
  ElideJSONSubstring("classes", json_str, buffer);
  ElideJSONSubstring("libraries", buffer, buffer);

  EXPECT_STREQ(
      "{\"type\":\"SourceReport\",\"ranges\":["

      // One range compiled with one hit (helper0).
      "{\"scriptIndex\":0,\"startPos\":0,\"endPos\":73,\"compiled\":true,"
      "\"coverage\":{\"hits\":[0,56],\"misses\":[]}},"

      // One range not compiled (helper1).
      "{\"scriptIndex\":0,\"startPos\":75,\"endPos\":86,\"compiled\":false},"

      // One range with two hits and a miss (main).
      "{\"scriptIndex\":0,\"startPos\":88,\"endPos\":156,\"compiled\":true,"
      "\"coverage\":{\"hits\":[88,115],\"misses\":[141]}},"

      // Nested range compiled (nestedHelper0).
      "{\"scriptIndex\":0,\"startPos\":14,\"endPos\":31,\"compiled\":true,"
      "\"coverage\":{\"hits\":[14],\"misses\":[]}},"

      // Nested range not compiled (nestedHelper1).
      "{\"scriptIndex\":0,\"startPos\":35,\"endPos\":52,\"compiled\":false}],"

      // Only one script in the script table.
      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\","
      "\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
      buffer);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_RestrictedRange) {
  // WARNING: This MUST be big enough for the serialized JSON string.
  const int kBufferSize = 1024;
  char buffer[kBufferSize];
  const char* kScript =
      "helper0() {\n"
      "  nestedHelper0() {}\n"
      "  nestedHelper1() {}\n"
      "  nestedHelper0();\n"
      "}\n"
      "helper1() {}\n"
      "main() {\n"
      "  if (true) {\n"
      "    helper0();\n"
      "  } else {\n"
      "    helper1();\n"
      "  }\n"
      "}";

  Library& lib = Library::Handle();
  lib ^= ExecuteScript(kScript);
  ASSERT(!lib.IsNull());
  const Script& script =
      Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));
  const Function& helper = Function::Handle(
      lib.LookupFunctionAllowPrivate(String::Handle(String::New("helper0"))));

  SourceReport report(SourceReport::kCoverage);
  JSONStream js;
  // Restrict the report to only helper0 and it's nested functions.
  report.PrintJSON(&js, script, helper.token_pos(), helper.end_token_pos());
  const char* json_str = js.ToCString();
  ASSERT(strlen(json_str) < kBufferSize);
  ElideJSONSubstring("classes", json_str, buffer);
  ElideJSONSubstring("libraries", buffer, buffer);

  EXPECT_STREQ(
      "{\"type\":\"SourceReport\",\"ranges\":["

      // One range compiled with one hit (helper0).
      "{\"scriptIndex\":0,\"startPos\":0,\"endPos\":73,\"compiled\":true,"
      "\"coverage\":{\"hits\":[0,56],\"misses\":[]}},"

      // Nested range compiled (nestedHelper0).
      "{\"scriptIndex\":0,\"startPos\":14,\"endPos\":31,\"compiled\":true,"
      "\"coverage\":{\"hits\":[14],\"misses\":[]}},"

      // Nested range not compiled (nestedHelper1).
      "{\"scriptIndex\":0,\"startPos\":35,\"endPos\":52,\"compiled\":false}],"

      // Only one script in the script table.
      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\","
      "\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
      buffer);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_AllFunctions) {
  const char* kScript =
      "helper0() {}\n"
      "helper1() {}\n"
      "main() {\n"
      "  if (true) {\n"
      "    helper0();\n"
      "  } else {\n"
      "    helper1();\n"
      "  }\n"
      "}";

  Library& lib = Library::Handle();
  lib ^= ExecuteScript(kScript);
  ASSERT(!lib.IsNull());

  SourceReport report(SourceReport::kCoverage);
  JSONStream js;

  // We generate a report with all functions in the VM.
  Script& null_script = Script::Handle();
  report.PrintJSON(&js, null_script);
  const char* result = js.ToCString();

  // Sanity check the header.
  EXPECT_SUBSTRING("{\"type\":\"SourceReport\",\"ranges\":[", result);

  // Make sure that the main function was found.
  EXPECT_SUBSTRING(
      "\"startPos\":26,\"endPos\":94,\"compiled\":true,"
      "\"coverage\":{\"hits\":[26,53],\"misses\":[79]}",
      result);

  // More than one script is referenced in the report.
  EXPECT_SUBSTRING("\"scriptIndex\":0", result);
  EXPECT_SUBSTRING("\"scriptIndex\":1", result);
  EXPECT_SUBSTRING("\"scriptIndex\":2", result);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_AllFunctions_ForceCompile) {
  const char* kScript =
      "helper0() {}\n"
      "helper1() {}\n"
      "main() {\n"
      "  if (true) {\n"
      "    helper0();\n"
      "  } else {\n"
      "    helper1();\n"
      "  }\n"
      "}";

  Library& lib = Library::Handle();
  lib ^= ExecuteScript(kScript);
  ASSERT(!lib.IsNull());

  SourceReport report(SourceReport::kCoverage, SourceReport::kForceCompile);
  JSONStream js;
  js.set_id_zone(thread->isolate()->EnsureDefaultServiceIdZone());

  // We generate a report with all functions in the VM.
  Script& null_script = Script::Handle();
  report.PrintJSON(&js, null_script);
  const char* result = js.ToCString();

  // Sanity check the header.
  EXPECT_SUBSTRING("{\"type\":\"SourceReport\",\"ranges\":[", result);

  // Make sure that the main function was found.
  EXPECT_SUBSTRING(
      "\"startPos\":26,\"endPos\":94,\"compiled\":true,"
      "\"coverage\":{\"hits\":[26,53],\"misses\":[79]}",
      result);

  // More than one script is referenced in the report.
  EXPECT_SUBSTRING("\"scriptIndex\":0", result);
  EXPECT_SUBSTRING("\"scriptIndex\":1", result);
  EXPECT_SUBSTRING("\"scriptIndex\":2", result);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_CallSites_SimpleCall) {
  // WARNING: This MUST be big enough for the serialized JSON string.
  const int kBufferSize = 2048;
  char buffer[kBufferSize];
  const char* kScript =
      "helper0() {}\n"
      "helper1() {}\n"
      "main() {\n"
      "  helper0();\n"
      "}";

  Library& lib = Library::Handle();
  lib ^= ExecuteScript(kScript);
  ASSERT(!lib.IsNull());
  const Script& script =
      Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));

  SourceReport report(SourceReport::kCallSites);
  JSONStream js;
  report.PrintJSON(&js, script);
  const char* json_str = js.ToCString();
  ASSERT(strlen(json_str) < kBufferSize);
  ElideJSONSubstring("classes", json_str, buffer);
  ElideJSONSubstring("libraries", buffer, buffer);
  EXPECT_STREQ(
      "{\"type\":\"SourceReport\",\"ranges\":["

      // One range compiled with no callsites (helper0).
      "{\"scriptIndex\":0,\"startPos\":0,\"endPos\":11,\"compiled\":true,"
      "\"callSites\":[]},"

      // One range not compiled (helper1).
      "{\"scriptIndex\":0,\"startPos\":13,\"endPos\":24,\"compiled\":false},"

      // One range compiled with one callsite (main).
      "{\"scriptIndex\":0,\"startPos\":26,\"endPos\":48,\"compiled\":true,"
      "\"callSites\":["
      "{\"name\":\"helper0\",\"tokenPos\":37,\"cacheEntries\":["
      "{\"target\":{\"type\":\"@Function\",\"fixedId\":true,\"id\":\"\","
      "\"name\":\"helper0\",\"owner\":{\"type\":\"@Library\",\"fixedId\":true,"
      "\"id\":\"\",\"name\":\"\",\"uri\":\"file:\\/\\/\\/test-lib\"},"
      "\"_kind\":\"RegularFunction\",\"static\":true,\"const\":false,"
      "\"implicit\":false,\"abstract\":false,"
      "\"_intrinsic\":false,\"_native\":false,\"isGetter\":false,"
      "\"isSetter\":false,\"location\":{\"type\":"
      "\"SourceLocation\",\"script\":{\"type\":\"@Script\",\"fixedId\":true,"
      "\"id\":\"\",\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"},"
      "\"tokenPos\":0,\"endTokenPos\":11,\"line\":1,\"column\":1}},\"count\":1}"
      "]}]}],"

      // One script in the script table.
      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\","
      "\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
      buffer);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_CallSites_PolymorphicCall) {
  // WARNING: This MUST be big enough for the serialized JSON string.
  const int kBufferSize = 4096;
  char buffer[kBufferSize];
  const char* kScript =
      "class Common {\n"
      "  func() {}\n"
      "}\n"
      "class Uncommon {\n"
      "  func() {}\n"
      "}\n"
      "helper(arg) {\n"
      "  arg.func();\n"
      "}\n"
      "main() {\n"
      "  Common common = new Common();\n"
      "  Uncommon uncommon = new Uncommon();\n"
      "  helper(common);\n"
      "  helper(common);\n"
      "  helper(uncommon);\n"
      "}";

  Library& lib = Library::Handle();
  lib ^= ExecuteScript(kScript);
  ASSERT(!lib.IsNull());
  const Script& script =
      Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));
  const Function& helper = Function::Handle(
      lib.LookupFunctionAllowPrivate(String::Handle(String::New("helper"))));

  SourceReport report(SourceReport::kCallSites);
  JSONStream js;
  report.PrintJSON(&js, script, helper.token_pos(), helper.end_token_pos());
  const char* json_str = js.ToCString();
  ASSERT(strlen(json_str) < kBufferSize);
  ElideJSONSubstring("classes", json_str, buffer);
  ElideJSONSubstring("libraries", buffer, buffer);
  EXPECT_STREQ(
      "{\"type\":\"SourceReport\",\"ranges\":["

      // One range...
      "{\"scriptIndex\":0,\"startPos\":60,\"endPos\":88,\"compiled\":true,"

      // With one call site...
      "\"callSites\":[{\"name\":\"dyn:func\",\"tokenPos\":80,\"cacheEntries\":["

      // First receiver: "Common", called twice.
      "{\"receiver\":{\"type\":\"@Class\",\"fixedId\":true,\"id\":\"\","
      "\"name\":\"Common\","
      "\"location\":{\"type\":\"SourceLocation\","
      "\"script\":{\"type\":\"@Script\","
      "\"fixedId\":true,\"id\":\"\","
      "\"uri\":\"file:\\/\\/\\/test-lib\","
      "\"_kind\":\"kernel\"},\"tokenPos\":0,\"endTokenPos\":27,\"line\":1,"
      "\"column\":1},"
      "\"library\":{\"type\":\"@Library\",\"fixedId\":true,"
      "\"id\":\"\",\"name\":\"\",\"uri\":\"file:\\/\\/\\/test-lib\"}},"

      "\"target\":{\"type\":\"@Function\",\"fixedId\":true,\"id\":\"\","
      "\"name\":\"func\","
      "\"owner\":{\"type\":\"@Class\",\"fixedId\":true,\"id\":\"\","
      "\"name\":\"Common\","
      "\"location\":{\"type\":\"SourceLocation\","
      "\"script\":{\"type\":\"@Script\","
      "\"fixedId\":true,\"id\":\"\","
      "\"uri\":\"file:\\/\\/\\/test-lib\","
      "\"_kind\":\"kernel\"},\"tokenPos\":0,\"endTokenPos\":27,\"line\":1,"
      "\"column\":1},"
      "\"library\":{\"type\":\"@Library\",\"fixedId\":true,"
      "\"id\":\"\",\"name\":\"\",\"uri\":\"file:\\/\\/\\/test-lib\"}"
      "},\"_kind\":\"RegularFunction\","
      "\"static\":false,\"const\":false,\"implicit\":false,\"abstract\":"
      "false,\"_intrinsic\":false,"
      "\"_native\":false,\"isGetter\":false,\"isSetter\":false,"
      "\"location\":{\"type\":\"SourceLocation\","
      "\"script\":{\"type\":\"@Script\",\"fixedId\":true,"
      "\"id\":\"\",\"uri\":\"file:\\/\\/\\/test-lib\","
      "\"_kind\":\"kernel\"},\"tokenPos\":17,\"endTokenPos\":25,\"line\":2,"
      "\"column\":3}},"

      "\"count\":2},"

      // Second receiver: "Uncommon", called once.
      "{\"receiver\":{\"type\":\"@Class\",\"fixedId\":true,\"id\":\"\","
      "\"name\":\"Uncommon\","
      "\"location\":{\"type\":\"SourceLocation\","
      "\"script\":{\"type\":\"@Script\","
      "\"fixedId\":true,\"id\":\"\","
      "\"uri\":\"file:\\/\\/\\/test-lib\","
      "\"_kind\":\"kernel\"},\"tokenPos\":29,\"endTokenPos\":58,\"line\":4,"
      "\"column\":1},"
      "\"library\":{\"type\":\"@Library\",\"fixedId\":true,"
      "\"id\":\"\",\"name\":\"\",\"uri\":\"file:\\/\\/\\/test-lib\"}},"

      "\"target\":{\"type\":\"@Function\",\"fixedId\":true,\"id\":\"\","
      "\"name\":\"func\","
      "\"owner\":{\"type\":\"@Class\",\"fixedId\":true,\"id\":\"\","
      "\"name\":\"Uncommon\","
      "\"location\":{\"type\":\"SourceLocation\","
      "\"script\":{\"type\":\"@Script\","
      "\"fixedId\":true,\"id\":\"\","
      "\"uri\":\"file:\\/\\/\\/test-lib\","
      "\"_kind\":\"kernel\"},\"tokenPos\":29,\"endTokenPos\":58,\"line\":4,"
      "\"column\":1},"
      "\"library\":{\"type\":\"@Library\",\"fixedId\":true,"
      "\"id\":\"\",\"name\":\"\",\"uri\":\"file:\\/\\/\\/test-lib\"}"
      "},\"_kind\":\"RegularFunction\","
      "\"static\":false,\"const\":false,\"implicit\":false,\"abstract\":"
      "false,\"_intrinsic\":false,"
      "\"_native\":false,\"isGetter\":false,\"isSetter\":false,"
      "\"location\":{\"type\":\"SourceLocation\","
      "\"script\":{\"type\":\"@Script\",\"fixedId\":true,"
      "\"id\":\"\",\"uri\":\"file:\\/\\/\\/test-lib\","
      "\"_kind\":\"kernel\"},\"tokenPos\":48,\"endTokenPos\":56,\"line\":5,"
      "\"column\":3}},"

      "\"count\":1}]}]}],"

      // One script in the script table.
      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\","
      "\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
      buffer);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_MultipleReports) {
  // WARNING: This MUST be big enough for the serialized JSON string.
  const int kBufferSize = 2048;
  char buffer[kBufferSize];
  const char* kScript =
      "helper0() {}\n"
      "helper1() {}\n"
      "main() {\n"
      "  helper0();\n"
      "}";

  Library& lib = Library::Handle();
  lib ^= ExecuteScript(kScript);
  ASSERT(!lib.IsNull());
  const Script& script =
      Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));

  SourceReport report(SourceReport::kCallSites | SourceReport::kCoverage);
  JSONStream js;
  report.PrintJSON(&js, script);
  const char* json_str = js.ToCString();
  ASSERT(strlen(json_str) < kBufferSize);
  ElideJSONSubstring("classes", json_str, buffer);
  ElideJSONSubstring("libraries", buffer, buffer);
  EXPECT_STREQ(
      "{\"type\":\"SourceReport\",\"ranges\":["

      // One range compiled with no callsites (helper0).
      "{\"scriptIndex\":0,\"startPos\":0,\"endPos\":11,\"compiled\":true,"
      "\"callSites\":[],"
      "\"coverage\":{\"hits\":[0],\"misses\":[]}},"

      // One range not compiled (helper1).
      "{\"scriptIndex\":0,\"startPos\":13,\"endPos\":24,\"compiled\":false},"

      // One range compiled with one callsite (main)m
      "{\"scriptIndex\":0,\"startPos\":26,\"endPos\":48,\"compiled\":true,"
      "\"callSites\":[{\"name\":\"helper0\",\"tokenPos\":37,\"cacheEntries\":[{"
      "\"target\":{\"type\":\"@Function\",\"fixedId\":true,\"id\":\"\","
      "\"name\":\"helper0\",\"owner\":{\"type\":\"@Library\",\"fixedId\":true,"
      "\"id\":\"\",\"name\":\"\",\"uri\":\"file:\\/\\/\\/test-lib\"},\"_"
      "kind\":\"RegularFunction\",\"static\":true,\"const\":false,\"implicit\":"
      "false,\"abstract\":false,\"_"
      "intrinsic\":false,\"_native\":false,\"isGetter\":false,"
      "\"isSetter\":false,\"location\":{\"type\":"
      "\"SourceLocation\",\"script\":{\"type\":\"@Script\",\"fixedId\":true,"
      "\"id\":\"\",\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"},"
      "\"tokenPos\":0,\"endTokenPos\":11,\"line\":1,\"column\":1}},\"count\":1}"
      "]}],\"coverage\":{"
      "\"hits\":[26,37],\"misses\":[]}}],"

      // One script in the script table.
      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\","
      "\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
      buffer);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_PossibleBreakpoints_Simple) {
  // WARNING: This MUST be big enough for the serialized JSON string.
  const int kBufferSize = 1024;
  char buffer[kBufferSize];
  const char* kScript =
      "helper0() {}\n"
      "helper1() {}\n"
      "main() {\n"
      "  if (true) {\n"
      "    helper0();\n"
      "  } else {\n"
      "    helper1();\n"
      "  }\n"
      "}";

  Library& lib = Library::Handle();
  lib ^= ExecuteScript(kScript);
  ASSERT(!lib.IsNull());
  const Script& script =
      Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));

  SourceReport report(SourceReport::kPossibleBreakpoints);
  JSONStream js;
  report.PrintJSON(&js, script);
  const char* json_str = js.ToCString();
  ASSERT(strlen(json_str) < kBufferSize);
  ElideJSONSubstring("classes", json_str, buffer);
  ElideJSONSubstring("libraries", buffer, buffer);
  EXPECT_STREQ(
      "{\"type\":\"SourceReport\",\"ranges\":["

      // helper0.
      "{\"scriptIndex\":0,\"startPos\":0,\"endPos\":11,\"compiled\":true,"
      "\"possibleBreakpoints\":[7,11]},"

      // One range not compiled (helper1).
      "{\"scriptIndex\":0,\"startPos\":13,\"endPos\":24,\"compiled\":false},"

      // main.
      "{\"scriptIndex\":0,\"startPos\":26,\"endPos\":94,\"compiled\":true,"
      "\"possibleBreakpoints\":[30,53,79,94]}],"

      // Only one script in the script table.
      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\","
      "\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
      buffer);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_Issue35453_NoSuchMethod) {
  // WARNING: This MUST be big enough for the serialized JSON string.
  const int kBufferSize = 1024;
  char buffer[kBufferSize];
  const char* kScript =
      "class Foo {\n"
      "  void bar() {}\n"
      "}\n"
      "class Unused implements Foo {\n"
      "  dynamic noSuchMethod(_) {}\n"
      "}\n"
      "void main() {\n"
      "  Foo().bar();\n"
      "}\n";

  Library& lib = Library::Handle();
  lib ^= ExecuteScript(kScript);
  ASSERT(!lib.IsNull());
  const Script& script =
      Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));

  SourceReport report(SourceReport::kCoverage, SourceReport::kForceCompile);
  JSONStream js;
  report.PrintJSON(&js, script);
  const char* json_str = js.ToCString();
  ASSERT(strlen(json_str) < kBufferSize);
  ElideJSONSubstring("classes", json_str, buffer);
  ElideJSONSubstring("libraries", buffer, buffer);
  EXPECT_STREQ(
      "{\"type\":\"SourceReport\",\"ranges\":["

      // Foo is hit.
      "{\"scriptIndex\":0,\"startPos\":14,\"endPos\":26,\"compiled\":true,"
      "\"coverage\":{\"hits\":[14],\"misses\":[]}},"

      // Unused is missed.
      "{\"scriptIndex\":0,\"startPos\":62,\"endPos\":87,\"compiled\":true,"
      "\"coverage\":{\"hits\":[],\"misses\":[62]}},"

      // Main is hit.
      "{\"scriptIndex\":0,\"startPos\":91,\"endPos\":120,\"compiled\":true,"
      "\"coverage\":{\"hits\":[91,107,113],\"misses\":[]}}],"

      // Only one script in the script table.
      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\","
      "\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
      buffer);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_Issue47017_Assert) {
  // WARNING: This MUST be big enough for the serialized JSON string.
  const int kBufferSize = 1024;
  char buffer[kBufferSize];
  const char* kScript =
      "void foo(Object? bar) {\n"
      "  assert(bar == null);\n"
      "}\n"
      "void main() {\n"
      "  foo(null);\n"
      "}\n";

  Library& lib = Library::Handle();
  const bool old_asserts = IsolateGroup::Current()->asserts();
  IsolateGroup::Current()->set_asserts(true);
  lib ^= ExecuteScript(kScript);
  IsolateGroup::Current()->set_asserts(old_asserts);
  ASSERT(!lib.IsNull());
  const Script& script =
      Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));

  SourceReport report(SourceReport::kCoverage, SourceReport::kForceCompile);
  JSONStream js;
  report.PrintJSON(&js, script);
  const char* json_str = js.ToCString();
  ASSERT(strlen(json_str) < kBufferSize);
  ElideJSONSubstring("classes", json_str, buffer);
  ElideJSONSubstring("libraries", buffer, buffer);
  EXPECT_STREQ(
      "{\"type\":\"SourceReport\",\"ranges\":["

      // Foo is hit, and the assert is hit.
      "{\"scriptIndex\":0,\"startPos\":0,\"endPos\":47,\"compiled\":true,"
      "\"coverage\":{\"hits\":[0,33],\"misses\":[]}},"

      // Main is hit.
      "{\"scriptIndex\":0,\"startPos\":49,\"endPos\":76,\"compiled\":true,"
      "\"coverage\":{\"hits\":[49,65],\"misses\":[]}}],"

      // Only one script in the script table.
      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\","
      "\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
      buffer);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_Issue47021_StaticOnlyClasses) {
  // WARNING: This MUST be big enough for the serialized JSON string.
  const int kBufferSize = 4096;
  char buffer[kBufferSize];
  const char* kScript =
      "abstract class AllStatic {\n"
      "  AllStatic._();\n"
      "  static int test() => 123;\n"
      "  static int foo = 456;\n"
      "}\n"
      "class NotAbstract {\n"
      "  NotAbstract._();\n"
      "  static int test() => 123;\n"
      "  static int foo = 456;\n"
      "}\n"
      "abstract class NotConstructor {\n"
      "  void _() {}\n"
      "  static int test() => 123;\n"
      "}\n"
      "abstract class NotPrivate {\n"
      "  NotPrivate();\n"
      "  static int test() => 123;\n"
      "}\n"
      "abstract class HasParams {\n"
      "  HasParams._(int i);\n"
      "  static int test() => 123;\n"
      "}\n"
      "abstract class HasFields {\n"
      "  HasFields._();\n"
      "  static int test() => 123;\n"
      "  int foo = 0;\n"
      "}\n"
      "abstract class HasNonStaticFunction {\n"
      "  HasNonStaticFunction._();\n"
      "  static int test() => 123;\n"
      "  int foo() => 456;\n"
      "}\n"
      "abstract class HasSubclass {\n"
      "  HasSubclass._();\n"
      "  static int test() => 123;\n"
      "  static int foo = 456;\n"
      "}\n"
      "abstract class Subclass extends HasSubclass {\n"
      "  Subclass() : super._();\n"
      "}\n"
      "void main() {\n"
      "  AllStatic.test();\n"
      "  NotAbstract.test();\n"
      "  NotConstructor.test();\n"
      "  NotPrivate.test();\n"
      "  HasParams.test();\n"
      "  HasFields.test();\n"
      "  HasNonStaticFunction.test();\n"
      "  HasSubclass.test();\n"
      "}\n";

  Library& lib = Library::Handle();
  lib ^= ExecuteScript(kScript);
  ASSERT(!lib.IsNull());
  const Script& script =
      Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));

  SourceReport report(SourceReport::kCoverage, SourceReport::kForceCompile);
  JSONStream js;
  report.PrintJSON(&js, script);
  const char* json_str = js.ToCString();
  ASSERT(strlen(json_str) < kBufferSize);
  ElideJSONSubstring("classes", json_str, buffer);
  ElideJSONSubstring("libraries", buffer, buffer);
  EXPECT_STREQ(
      "{\"type\":\"SourceReport\",\"ranges\":["

      // Subclass() is missed.
      "{\"scriptIndex\":0,\"startPos\":775,\"endPos\":797,\"compiled\":true,"
      "\"coverage\":{\"hits\":[],\"misses\":[775,794]}},"

      // AllStatic.test() is hit. AllStatic._() is ignored (would be pos: 29).
      "{\"scriptIndex\":0,\"startPos\":46,\"endPos\":70,\"compiled\":true,"
      "\"coverage\":{\"hits\":[46],\"misses\":[]}},"

      // HasSubclass._() is missed, not ignored.
      "{\"scriptIndex\":0,\"startPos\":656,\"endPos\":671,\"compiled\":true,"
      "\"coverage\":{\"hits\":[],\"misses\":[656]}},"

      // HasSubclass.test() is hit.
      "{\"scriptIndex\":0,\"startPos\":675,\"endPos\":699,\"compiled\":true,"
      "\"coverage\":{\"hits\":[675],\"misses\":[]}},"

      // HasParams._(int i) is missed, not ignored.
      "{\"scriptIndex\":0,\"startPos\":370,\"endPos\":388,\"compiled\":true,"
      "\"coverage\":{\"hits\":[],\"misses\":[370]}},"

      // HasParams.test() is hit.
      "{\"scriptIndex\":0,\"startPos\":392,\"endPos\":416,\"compiled\":true,"
      "\"coverage\":{\"hits\":[392],\"misses\":[]}},"

      // NotAbstract._() is missed, not ignored.
      "{\"scriptIndex\":0,\"startPos\":120,\"endPos\":135,\"compiled\":true,"
      "\"coverage\":{\"hits\":[],\"misses\":[120]}},"

      // NotAbstract.test() is hit.
      "{\"scriptIndex\":0,\"startPos\":139,\"endPos\":163,\"compiled\":true,"
      "\"coverage\":{\"hits\":[139],\"misses\":[]}},"

      // HasFields._() is missed, not ignored.
      "{\"scriptIndex\":0,\"startPos\":449,\"endPos\":462,\"compiled\":true,"
      "\"coverage\":{\"hits\":[],\"misses\":[449]}},"

      // HasFields.test() is hit.
      "{\"scriptIndex\":0,\"startPos\":466,\"endPos\":490,\"compiled\":true,"
      "\"coverage\":{\"hits\":[466],\"misses\":[]}},"

      // NotPrivate() is missed, not ignored.
      "{\"scriptIndex\":0,\"startPos\":297,\"endPos\":309,\"compiled\":true,"
      "\"coverage\":{\"hits\":[],\"misses\":[297]}},"

      // NotPrivate.test() is hit.
      "{\"scriptIndex\":0,\"startPos\":313,\"endPos\":337,\"compiled\":true,"
      "\"coverage\":{\"hits\":[313],\"misses\":[]}},"

      // HasNonStaticFunction._() is missed, not ignored.
      "{\"scriptIndex\":0,\"startPos\":549,\"endPos\":573,\"compiled\":true,"
      "\"coverage\":{\"hits\":[],\"misses\":[549]}},"

      // HasNonStaticFunction.test() is hit.
      "{\"scriptIndex\":0,\"startPos\":577,\"endPos\":601,\"compiled\":true,"
      "\"coverage\":{\"hits\":[577],\"misses\":[]}},"

      // HasNonStaticFunction.foo() is missed.
      "{\"scriptIndex\":0,\"startPos\":605,\"endPos\":621,\"compiled\":true,"
      "\"coverage\":{\"hits\":[],\"misses\":[605]}},"

      // NotConstructor._() is missed, not ignored.
      "{\"scriptIndex\":0,\"startPos\":225,\"endPos\":235,\"compiled\":true,"
      "\"coverage\":{\"hits\":[],\"misses\":[225]}},"

      // NotConstructor.test() is hit.
      "{\"scriptIndex\":0,\"startPos\":239,\"endPos\":263,\"compiled\":true,"
      "\"coverage\":{\"hits\":[239],\"misses\":[]}},"

      // Main is hit.
      "{\"scriptIndex\":0,\"startPos\":801,\"endPos\":996,\"compiled\":true,"
      "\"coverage\":{\"hits\":"
      "[801,827,849,874,895,915,935,966,988],\"misses\":[]}}],"

      // Only one script in the script table.
      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\","
      "\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
      buffer);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_IssueCov341_LateFinalVars) {
  // https://github.com/dart-lang/coverage/issues/341
  // WARNING: This MUST be big enough for the serialized JSON string.
  const int kBufferSize = 1024;
  char buffer[kBufferSize];
  const char* kScript =
      "int foo(bool bar) {\n"
      "  late final int baz;\n"
      "  if (bar) {\n"
      "    baz = 123;\n"
      "  } else {\n"
      "    baz = 456;\n"
      "  }\n"
      "  return baz;\n"
      "}\n"
      "main() {\n"
      "  foo(true);\n"
      "  foo(false);\n"
      "}\n";

  Library& lib = Library::Handle();
  lib ^= ExecuteScript(kScript);
  ASSERT(!lib.IsNull());
  const Script& script =
      Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));

  SourceReport report(SourceReport::kCoverage, SourceReport::kForceCompile);
  JSONStream js;
  report.PrintJSON(&js, script);
  const char* json_str = js.ToCString();
  ASSERT(strlen(json_str) < kBufferSize);
  ElideJSONSubstring("classes", json_str, buffer);
  ElideJSONSubstring("libraries", buffer, buffer);
  EXPECT_STREQ(
      "{\"type\":\"SourceReport\",\"ranges\":["

      // foo is hit, but the late variable sets and gets are ignored.
      "{\"scriptIndex\":0,\"startPos\":0,\"endPos\":114,\"compiled\":true,"
      "\"coverage\":{\"hits\":[0],\"misses\":[]}},"

      // Main is hit.
      "{\"scriptIndex\":0,\"startPos\":116,\"endPos\":152,\"compiled\":true,\""
      "coverage\":{\"hits\":[116,127,140],\"misses\":[]}}],"

      // Only one script in the script table.
      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\","
      "\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
      buffer);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_IssueCov386_EnhancedEnums) {
  // https://github.com/dart-lang/coverage/issues/386
  // https://github.com/dart-lang/coverage/issues/377
  // WARNING: This MUST be big enough for the serialized JSON string.
  const int kBufferSize = 1024;
  char buffer[kBufferSize];
  const char* kScript =
      "enum FoodType {\n"
      "  candy();\n"
      "  const FoodType();\n"
      "  factory FoodType.candyFactory() => candy;\n"
      "}\n"
      "void main() {\n"
      "  final food = FoodType.candyFactory();\n"
      "}\n";

  Library& lib = Library::Handle();
  lib ^= ExecuteScript(kScript);
  ASSERT(!lib.IsNull());
  const Script& script =
      Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));

  SourceReport report(SourceReport::kCoverage, SourceReport::kForceCompile);
  JSONStream js;
  report.PrintJSON(&js, script);
  const char* json_str = js.ToCString();
  ASSERT(strlen(json_str) < kBufferSize);
  ElideJSONSubstring("classes", json_str, buffer);
  ElideJSONSubstring("libraries", buffer, buffer);
  EXPECT_STREQ(
      "{\"type\":\"SourceReport\",\"ranges\":["

      // Main is hit.
      "{\"scriptIndex\":0,\"startPos\":49,\"endPos\":89,\"compiled\":true,"
      "\"coverage\":{\"hits\":[49],\"misses\":[]}},"

      // The enum's constructor, and toString, are not included in the hitmap,
      // but the factory is included.
      "{\"scriptIndex\":0,\"startPos\":93,\"endPos\":147,\"compiled\":true,"
      "\"coverage\":{\"hits\":[93,131],\"misses\":[]}}],"

      // Only one script in the script table.
      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\","
      "\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
      buffer);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_Regress95008_RedirectingFactory) {
  // WARNING: This MUST be big enough for the serialized JSON string.
  const int kBufferSize = 1024;
  char buffer[kBufferSize];
  const char* kScript = R"(
class A {
  A();
  factory A.foo(int i) = B; // LINE_A
}

class B extends A {
  int i;
  B(this.i); // LINE_B
}

main() {
  A.foo(42);
}
)";

  Library& lib = Library::Handle();
  lib ^= ExecuteScript(kScript);
  ASSERT(!lib.IsNull());
  const Script& script =
      Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));

  SourceReport report(SourceReport::kCoverage);
  JSONStream js;
  report.PrintJSON(&js, script);
  const char* json_str = js.ToCString();
  ASSERT(strlen(json_str) < kBufferSize);
  ElideJSONSubstring("classes", json_str, buffer);
  ElideJSONSubstring("libraries", buffer, buffer);
  EXPECT_STREQ(
      "{\"type\":\"SourceReport\",\"ranges\":["

      // A()
      "{\"scriptIndex\":0,\"startPos\":13,\"endPos\":16,\"compiled\":true,"
      "\"coverage\":{\"hits\":[13],\"misses\":[]}},"

      // B()
      "{\"scriptIndex\":0,\"startPos\":90,\"endPos\":99,\"compiled\":true,"
      "\"coverage\":{\"hits\":[90],\"misses\":[]}},"

      // main
      "{\"scriptIndex\":0,\"startPos\":114,\"endPos\":136,\"compiled\":true,"
      "\"coverage\":{\"hits\":[114,127],\"misses\":[]}}],"

      // Only one script in the script table.
      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\","
      "\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
      buffer);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_Regress53519_Destructuring) {
  // WARNING: This MUST be big enough for the serialized JSON string.
  const int kBufferSize = 1024;
  char buffer[kBufferSize];
  const char* kScript = R"(
main() {
  destructure({
    'hello': 'world',
    'count': [1, 2, 3],
  });
}

String destructure(Map<String, dynamic> map) {
  final {'hello': world, 'count': count} = map;
  return 'Hello $world, count: $count';
}
)";

  Library& lib = Library::Handle();
  lib ^= ExecuteScript(kScript);
  ASSERT(!lib.IsNull());
  const Script& script =
      Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));

  SourceReport report(SourceReport::kCoverage);
  JSONStream js;
  report.PrintJSON(&js, script);
  const char* json_str = js.ToCString();
  ASSERT(strlen(json_str) < kBufferSize);
  ElideJSONSubstring("classes", json_str, buffer);
  ElideJSONSubstring("libraries", buffer, buffer);
  EXPECT_STREQ(
      "{\"type\":\"SourceReport\",\"ranges\":["

      // main
      "{\"scriptIndex\":0,\"startPos\":1,\"endPos\":78,\"compiled\":true,"
      "\"coverage\":{\"hits\":[1,12,24,61],\"misses\":[]}},"

      // destructure
      "{\"scriptIndex\":0,\"startPos\":81,\"endPos\":216,\"compiled\":true,"
      "\"coverage\":{\"hits\":[81,144,160,185],\"misses\":[]}}],"

      // Only one script in the script table.
      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\","
      "\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
      buffer);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_BranchCoverage_if) {
  // WARNING: This MUST be big enough for the serialized JSON string.
  const int kBufferSize = 1024;
  char buffer[kBufferSize];
  const char* kScript = R"(
int ifTest(int x) {
  if (x > 0) {
    if (x > 10) {
      return 10;
    } else {
      return 1;
    }
  } else {
    return 0;
  }
}

main() {
  ifTest(1);
}
)";

  Library& lib = Library::Handle();
  const bool old_branch_coverage = IsolateGroup::Current()->branch_coverage();
  IsolateGroup::Current()->set_branch_coverage(true);
  lib ^= ExecuteScript(kScript);
  IsolateGroup::Current()->set_branch_coverage(old_branch_coverage);
  ASSERT(!lib.IsNull());
  const Script& script =
      Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));

  SourceReport report(SourceReport::kBranchCoverage);
  JSONStream js;
  report.PrintJSON(&js, script);
  const char* json_str = js.ToCString();
  ASSERT(strlen(json_str) < kBufferSize);
  ElideJSONSubstring("classes", json_str, buffer);
  ElideJSONSubstring("libraries", buffer, buffer);
  EXPECT_STREQ(
      "{\"type\":\"SourceReport\",\"ranges\":["

      // In ifTest, the outer true case is hit, the inner true case is missed,
      // the inner false case is hit, and the outer false case is missed.
      "{\"scriptIndex\":0,\"startPos\":1,\"endPos\":135,\"compiled\":true,"
      "\"branchCoverage\":{\"hits\":[1,34,82],\"misses\":[52,115]}},"

      // Main is hit.
      "{\"scriptIndex\":0,\"startPos\":138,\"endPos\":160,\"compiled\":true,"
      "\"branchCoverage\":{\"hits\":[138],\"misses\":[]}}],"

      // Only one script in the script table.
      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\","
      "\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
      buffer);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_BranchCoverage_loops) {
  // WARNING: This MUST be big enough for the serialized JSON string.
  const int kBufferSize = 1024;
  char buffer[kBufferSize];
  const char* kScript = R"(
int loopTest() {
  var x = 0;

  while (x < 10) {
    ++x;
  }

  do {
    ++x;
  } while (false);

  for (int i = 0; i < 10; ++i) {
    ++x;
  }

  for (final i in [1, 2, 3]) {
    ++x;
  }

  return x;
}

main() {
  loopTest();
}
)";

  Library& lib = Library::Handle();
  const bool old_branch_coverage = IsolateGroup::Current()->branch_coverage();
  IsolateGroup::Current()->set_branch_coverage(true);
  lib ^= ExecuteScript(kScript);
  IsolateGroup::Current()->set_branch_coverage(old_branch_coverage);
  ASSERT(!lib.IsNull());
  const Script& script =
      Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));

  SourceReport report(SourceReport::kBranchCoverage);
  JSONStream js;
  report.PrintJSON(&js, script);
  const char* json_str = js.ToCString();
  ASSERT(strlen(json_str) < kBufferSize);
  ElideJSONSubstring("classes", json_str, buffer);
  ElideJSONSubstring("libraries", buffer, buffer);
  EXPECT_STREQ(
      "{\"type\":\"SourceReport\",\"ranges\":["

      // In loopTest, the while loop, do-while loop, for loop, and for-in loop
      // are all hit.
      "{\"scriptIndex\":0,\"startPos\":1,\"endPos\":205,\"compiled\":true,"
      "\"branchCoverage\":{\"hits\":[1,49,70,132,177],\"misses\":[]}},"

      // Main is hit.
      "{\"scriptIndex\":0,\"startPos\":208,\"endPos\":231,\"compiled\":true,"
      "\"branchCoverage\":{\"hits\":[208],\"misses\":[]}}],"

      // Only one script in the script table.
      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\","
      "\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
      buffer);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_BranchCoverage_switch) {
  // WARNING: This MUST be big enough for the serialized JSON string.
  const int kBufferSize = 1024;
  char buffer[kBufferSize];
  const char* kScript = R"(
int switchTest(int x) {
  switch (x) {
    case 0:
      return 10;
    case 1:
      return 20;
    default:
      return 30;
  }
}

main() {
  switchTest(1);
}
)";

  Library& lib = Library::Handle();
  const bool old_branch_coverage = IsolateGroup::Current()->branch_coverage();
  IsolateGroup::Current()->set_branch_coverage(true);
  lib ^= ExecuteScript(kScript);
  IsolateGroup::Current()->set_branch_coverage(old_branch_coverage);
  ASSERT(!lib.IsNull());
  const Script& script =
      Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));

  SourceReport report(SourceReport::kBranchCoverage);
  JSONStream js;
  report.PrintJSON(&js, script);
  const char* json_str = js.ToCString();
  ASSERT(strlen(json_str) < kBufferSize);
  ElideJSONSubstring("classes", json_str, buffer);
  ElideJSONSubstring("libraries", buffer, buffer);
  EXPECT_STREQ(
      "{\"type\":\"SourceReport\",\"ranges\":["

      // In switchTest, the 1 case is hit and the others are missed.
      "{\"scriptIndex\":0,\"startPos\":1,\"endPos\":132,\"compiled\":true,"
      "\"branchCoverage\":{\"hits\":[1,73],\"misses\":[44,102]}},"

      // Main is hit.
      "{\"scriptIndex\":0,\"startPos\":135,\"endPos\":161,\"compiled\":true,"
      "\"branchCoverage\":{\"hits\":[135],\"misses\":[]}}],"

      // Only one script in the script table.
      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\","
      "\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
      buffer);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_BranchCoverage_try) {
  // WARNING: This MUST be big enough for the serialized JSON string.
  const int kBufferSize = 1024;
  char buffer[kBufferSize];
  const char* kScript = R"(
void tryTestInner() {
  try {
    throw "abc";
  } catch (e) {
  } finally {
  }

  try {
    throw "def";
  } finally {
  }
}

void tryTestOuter() {
  try {
    tryTestInner();
  } catch (e) {
  }
}

main() {
  tryTestOuter();
}
)";

  Library& lib = Library::Handle();
  const bool old_branch_coverage = IsolateGroup::Current()->branch_coverage();
  IsolateGroup::Current()->set_branch_coverage(true);
  lib ^= ExecuteScript(kScript);
  IsolateGroup::Current()->set_branch_coverage(old_branch_coverage);
  ASSERT(!lib.IsNull());
  const Script& script =
      Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));

  SourceReport report(SourceReport::kBranchCoverage);
  JSONStream js;
  report.PrintJSON(&js, script);
  const char* json_str = js.ToCString();
  ASSERT(strlen(json_str) < kBufferSize);
  ElideJSONSubstring("classes", json_str, buffer);
  ElideJSONSubstring("libraries", buffer, buffer);
  EXPECT_STREQ(
      "{\"type\":\"SourceReport\",\"ranges\":["

      // In tryTestInner, the try/catch/finally and the try/finally are all hit,
      // and the try/finally rethrows its exception.
      "{\"scriptIndex\":0,\"startPos\":1,\"endPos\":126,\"compiled\":true,"
      "\"branchCoverage\":{\"hits\":[1,29,62,76,89,120],\"misses\":[]}},"

      // In tryTestOuter, the exception thrown by tryTestInner causes both the
      // try and the catch to be hit.
      "{\"scriptIndex\":0,\"startPos\":129,\"endPos\":199,\"compiled\":true,"
      "\"branchCoverage\":{\"hits\":[129,157,193],\"misses\":[]}},"

      // Main is hit.
      "{\"scriptIndex\":0,\"startPos\":202,\"endPos\":229,\"compiled\":true,"
      "\"branchCoverage\":{\"hits\":[202],\"misses\":[]}}],"

      // Only one script in the script table.
      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\","
      "\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
      buffer);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_constConstructor) {
  // WARNING: This MUST be big enough for the serialized JSON string.
  const int kBufferSize = 1024;
  char buffer[kBufferSize];
  const char* kScript = R"(
abstract class SuperClass {
  const SuperClass();
}

class Class extends SuperClass {
  const Class() :
      super();
  int method() => 123;
}

main() {
  const Class().method();
}
)";

  Library& lib = Library::Handle();
  const bool old_branch_coverage = IsolateGroup::Current()->branch_coverage();
  IsolateGroup::Current()->set_branch_coverage(true);
  lib ^= ExecuteScript(kScript);
  IsolateGroup::Current()->set_branch_coverage(old_branch_coverage);
  ASSERT(!lib.IsNull());
  const Script& script =
      Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));

  SourceReport report(SourceReport::kCoverage, SourceReport::kForceCompile);
  JSONStream js;
  report.PrintJSON(&js, script);
  const char* json_str = js.ToCString();
  ASSERT(strlen(json_str) < kBufferSize);
  ElideJSONSubstring("classes", json_str, buffer);
  ElideJSONSubstring("libraries", buffer, buffer);
  EXPECT_STREQ(
      "{\"type\":\"SourceReport\",\"ranges\":["

      // The super class constructor is hit.
      "{\"scriptIndex\":0,\"startPos\":31,\"endPos\":49,\"compiled\":true,"
      "\"coverage\":{\"hits\":[31],\"misses\":[]}},"

      // The class constructor is hit, as well as the super() call.
      "{\"scriptIndex\":0,\"startPos\":89,\"endPos\":118,\"compiled\":true,"
      "\"coverage\":{\"hits\":[89,111],\"misses\":[]}},"

      // The method is called.
      "{\"scriptIndex\":0,\"startPos\":122,\"endPos\":141,\"compiled\":true,"
      "\"coverage\":{\"hits\":[122],\"misses\":[]}},"

      // Main is hit.
      "{\"scriptIndex\":0,\"startPos\":146,\"endPos\":181,\"compiled\":true,"
      "\"coverage\":{\"hits\":[146,171],\"misses\":[]}}],"

      // Only one script in the script table.
      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,"
      "\"id\":\"\",\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
      buffer);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_BranchCoverage_constConstructor) {
  // WARNING: This MUST be big enough for the serialized JSON string.
  const int kBufferSize = 1024;
  char buffer[kBufferSize];
  const char* kScript = R"(
abstract class SuperClass {
  const SuperClass();
}

class Class extends SuperClass {
  const Class() :
      super();
  int method() => 123;
}

main() {
  const Class().method();
}
)";

  Library& lib = Library::Handle();
  const bool old_branch_coverage = IsolateGroup::Current()->branch_coverage();
  IsolateGroup::Current()->set_branch_coverage(true);
  lib ^= ExecuteScript(kScript);
  IsolateGroup::Current()->set_branch_coverage(old_branch_coverage);
  ASSERT(!lib.IsNull());
  const Script& script =
      Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));

  SourceReport report(SourceReport::kBranchCoverage,
                      SourceReport::kForceCompile);
  JSONStream js;
  report.PrintJSON(&js, script);
  const char* json_str = js.ToCString();
  ASSERT(strlen(json_str) < kBufferSize);
  ElideJSONSubstring("classes", json_str, buffer);
  ElideJSONSubstring("libraries", buffer, buffer);
  EXPECT_STREQ(
      "{\"type\":\"SourceReport\",\"ranges\":["

      // The super class constructor is hit.
      "{\"scriptIndex\":0,\"startPos\":31,\"endPos\":49,\"compiled\":true,"
      "\"branchCoverage\":{\"hits\":[31],\"misses\":[]}},"

      // The class constructor is hit.
      "{\"scriptIndex\":0,\"startPos\":89,\"endPos\":118,\"compiled\":true,"
      "\"branchCoverage\":{\"hits\":[89],\"misses\":[]}},"

      // The method is called.
      "{\"scriptIndex\":0,\"startPos\":122,\"endPos\":141,\"compiled\":true,"
      "\"branchCoverage\":{\"hits\":[122],\"misses\":[]}},"

      // Main is hit.
      "{\"scriptIndex\":0,\"startPos\":146,\"endPos\":181,\"compiled\":true,"
      "\"branchCoverage\":{\"hits\":[146],\"misses\":[]}}],"

      // Only one script in the script table.
      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\",\""
      "uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
      buffer);
}

ISOLATE_UNIT_TEST_CASE(SourceReport_BranchCoverage_finallyEdgeCases) {
  // Regression test for https://github.com/dart-lang/tools/issues/2241
  // The cases covered in this test are the same as those listed in
  // StreamingFlowGraphBuilder::BuildTryFinally.
  const int kBufferSize = 1024;
  char buffer[kBufferSize];
  const char* kScript = R"(
int viaReturn() {
  try {
    return 6 * 7;
  } finally {
  }
}

void viaBreak() {
  for (int i = 0; i < 10; ++i) {
    try {
      break;
    } finally {
    }
  }
}

void viaContinue() {
  for (int i = 0; i < 10; ++i) {
    try {
      continue;
    } finally {
    }
  }
}

int viaSwitchContinue(int x) {
  switch (x) {
    case 123:
      try {
        continue otherCase;
      } finally {
      }
    otherCase:
    case 456:
      return 789;
  }
  return 0;
}

void viaOpenBody() {
  try {
  } finally {
  }
}

void viaThrow() {
  try {
    throw 123;
  } catch (e) {
  } finally {
  }
}

main() {
  viaReturn();
  viaBreak();
  viaContinue();
  viaSwitchContinue(123);
  viaOpenBody();
  viaThrow();
}
)";

  Library& lib = Library::Handle();
  const bool old_branch_coverage = IsolateGroup::Current()->branch_coverage();
  IsolateGroup::Current()->set_branch_coverage(true);
  lib ^= ExecuteScript(kScript);
  IsolateGroup::Current()->set_branch_coverage(old_branch_coverage);
  ASSERT(!lib.IsNull());
  const Script& script =
      Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));

  SourceReport report(SourceReport::kBranchCoverage,
                      SourceReport::kForceCompile);
  JSONStream js;
  report.PrintJSON(&js, script);
  const char* json_str = js.ToCString();
  ASSERT(strlen(json_str) < kBufferSize);
  ElideJSONSubstring("classes", json_str, buffer);
  ElideJSONSubstring("libraries", buffer, buffer);
  EXPECT_STREQ(
      "{\"type\":\"SourceReport\",\"ranges\":["

      // viaReturn's finally is hit.
      "{\"scriptIndex\":0,\"startPos\":1,\"endPos\":63,\"compiled\":true,"
      "\"branchCoverage\":{\"hits\":[1,25,57],\"misses\":[]}},"

      // viaBreak's finally is hit.
      "{\"scriptIndex\":0,\"startPos\":66,\"endPos\":166,\"compiled\":true,"
      "\"branchCoverage\":{\"hits\":[66,115,125,154],\"misses\":[]}},"

      // viaContinue's finally is hit.
      "{\"scriptIndex\":0,\"startPos\":169,\"endPos\":275,\"compiled\":true,"
      "\"branchCoverage\":{\"hits\":[169,221,231,263],\"misses\":[]}},"

      // viaSwitchContinue's finally is hit.
      "{\"scriptIndex\":0,\"startPos\":278,\"endPos\":467,\"compiled\":true,"
      "\"branchCoverage\":{\"hits\":[278,328,348,394,408],\"misses\":[]}},"

      // viaOpenBody's finally is hit.
      "{\"scriptIndex\":0,\"startPos\":470,\"endPos\":517,\"compiled\":true,"
      "\"branchCoverage\":{\"hits\":[470,497,511],\"misses\":[]}},"

      // viaThrow's finally is hit.
      "{\"scriptIndex\":0,\"startPos\":520,\"endPos\":595,\"compiled\":true,"
      "\"branchCoverage\":{\"hits\":[520,544,575,589],\"misses\":[]}},"

      // Main is hit.
      "{\"scriptIndex\":0,\"startPos\":598,\"endPos\":710,\"compiled\":true,"
      "\"branchCoverage\":{\"hits\":[598],\"misses\":[]}}],"

      // Only one script in the script table.
      "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\",\""
      "uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",

      buffer);
}

#endif  // !PRODUCT

}  // namespace dart
