unittests/SourceMap/SourceMapTest.cpp (475 lines of code) (raw):
/*
* 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.
*/
#include "hermes/Parser/JSONParser.h"
#include "hermes/SourceMap/SourceMapGenerator.h"
#include "hermes/SourceMap/SourceMapParser.h"
#include "hermes/Support/Base64vlq.h"
#include "hermes/Support/SimpleDiagHandler.h"
#include "llvh/Support/MemoryBuffer.h"
#include "llvh/Support/raw_ostream.h"
#include "gtest/gtest.h"
using namespace hermes;
using namespace hermes::parser;
namespace {
/// Test data from:
/// https://github.com/mozilla/source-map/blob/master/test/util.js
const char *TestMap = R"#({
"version": 3,
"file": "min.js",
"names": ["bar", "baz", "n"],
"sources": ["one.js", "two.js"],
"sourceRoot": "/the/root/",
"mappings": "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA"
})#";
// The following source map is missing enclosing brace so is invalid.
const char *InvalidJsonMap = R"#({
"version": 3,
"file": "min.js",
"names": ["bar", "baz", "n"],
"sources": ["one.js", "two.js"],
"sourceRoot": "/the/root/",
)#";
// No "sourceRoot" field.
const char *TestMapNoSourceRoot = R"#({
"version": 3,
"file": "min.js",
"names": ["bar", "baz", "n"],
"sources": ["one.js", "two.js"],
"mappings": "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA"
})#";
// Empty "sourceRoot" field.
const char *TestMapEmptySourceRoot = R"#({
"version": 3,
"file": "min.js",
"names": ["bar", "baz", "n"],
"sources": ["one.js", "two.js"],
"sourceRoot": "",
"mappings": "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA"
})#";
// Empty "sourceRoot" field.
const char *TestMapEmptyLines = R"#({
"version": 3,
"file": "min.js",
"names": ["bar", "baz", "n"],
"sources": ["one.js", "two.js"],
"mappings": ";;CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA"
})#";
/// Helper to return a Segment.
SourceMap::Segment loc(
int32_t address /* 0-based */,
int32_t sourceIndex,
int32_t line /* 1-based */,
int32_t column /* 0-based */,
llvh::Optional<int32_t> nameIndex = llvh::None) {
return SourceMap::Segment{address, sourceIndex, line - 1, column, nameIndex};
}
/// Helper to return a segment with no represented location.
SourceMap::Segment loc(int32_t address) {
SourceMap::Segment seg;
seg.generatedColumn = address;
return seg;
}
void verifySegment(
SourceMap &sourceMap,
int generatedLine,
const std::vector<std::string> &sources,
const SourceMap::Segment &segment) {
llvh::Optional<SourceMapTextLocation> locOpt =
sourceMap.getLocationForAddress(
generatedLine, segment.generatedColumn + 1);
if (segment.representedLocation.hasValue()) {
EXPECT_TRUE(locOpt.hasValue());
EXPECT_EQ(
locOpt.getValue().fileName,
sources[segment.representedLocation->sourceIndex]);
EXPECT_EQ(
locOpt.getValue().line, segment.representedLocation->lineIndex + 1);
EXPECT_EQ(
locOpt.getValue().column, segment.representedLocation->columnIndex + 1);
} else {
EXPECT_FALSE(locOpt.hasValue());
}
}
TEST(SourceMap, Basic) {
SourceMapGenerator map;
EXPECT_EQ(map.getMappingsLines().size(), 0u);
std::vector<std::string> sources{"file1", "file2"};
for (const auto &source : sources) {
map.addSource(source);
}
std::vector<SourceMap::Segment> segmentsList[] = {
{
loc(0, 0, 1, 1), // addr 1:0 -> file1:1:1
loc(2, 0, 2, 1), // addr 1:2 -> file1:2:1
loc(3, 0, 3, 1), // addr 1:3 -> file1:3:1
loc(4), // addr 1:4 -> unmapped
loc(5, 0, 3, 2), // addr 1:5 -> file1:3:2
},
{
loc(0, 1, 6, 6), // addr 2:0 -> file2:6:6
loc(1, 1, 7, 2), // addr 2:1 -> file2:7:2
loc(3, 1, 7, 3), // addr 2:3 -> file2:7:3
loc(5, 1, 8, 1), // addr 2:5 -> file2:8:1
}};
uint32_t i = 0;
for (const auto &segments : segmentsList) {
map.addMappingsLine(segments, i++);
}
ASSERT_EQ(map.getMappingsLines().size(), 2u);
EXPECT_EQ(map.getMappingsLines()[0].size(), 5u);
EXPECT_EQ(map.getMappingsLines()[1].size(), 4u);
std::vector<uint32_t> functionOffsets1 = {20, 23, 50, 789};
std::vector<uint32_t> functionOffsets2 = {1, 255, 300, 500};
map.addFunctionOffsets(std::move(functionOffsets1), 0);
map.addFunctionOffsets(std::move(functionOffsets2), 1);
std::string storage;
llvh::raw_string_ostream OS(storage);
map.outputAsJSON(OS);
EXPECT_EQ(
OS.str(),
R"#({"version":3,"sources":["file1","file2"],"mappings":"AAAC,EACA,CACA,C,CAAC;ACGI,CACJ,EAAC,EACF;","x_hermes_function_offsets":{"0":[20,23,50,789],"1":[1,255,300,500]}})#");
SourceErrorManager sm;
SimpleDiagHandlerRAII diagHandler(sm);
std::unique_ptr<SourceMap> sourceMap = SourceMapParser::parse(storage, sm);
for (uint32_t line = 0; line < sizeof(segmentsList) / sizeof(segmentsList[0]);
++line) {
const auto &segments = segmentsList[line];
for (uint32_t i = 0; i < segments.size(); ++i) {
verifySegment(
*sourceMap, /*generatedLine*/ line + 1, sources, segments[i]);
}
}
}
TEST(SourceMap, InvalidJsonMapTest) {
SourceErrorManager sm;
SimpleDiagHandlerRAII diagHandler(sm);
std::unique_ptr<SourceMap> sourceMap =
SourceMapParser::parse(InvalidJsonMap, sm);
EXPECT_TRUE(sourceMap == nullptr);
};
/// "test that the `sources` field has the original sources" from
/// https://github.com/mozilla/source-map/blob/master/test/test-source-map-consumer.js
TEST(SourceMap, SourcesField) {
auto verifySources = [](const char *sourceMapContent,
const std::vector<std::string> &expected) {
SourceErrorManager sm;
SimpleDiagHandlerRAII diagHandler(sm);
std::unique_ptr<SourceMap> sourceMap =
SourceMapParser::parse(sourceMapContent, sm);
std::vector<std::string> sources = sourceMap->getAllFullPathSources();
EXPECT_EQ(sources.size(), expected.size());
for (uint32_t i = 0; i < expected.size(); ++i) {
EXPECT_EQ(sources[i], expected[i]);
}
};
verifySources(TestMap, {"/the/root/one.js", "/the/root/two.js"});
verifySources(TestMapNoSourceRoot, {"one.js", "two.js"});
verifySources(TestMapEmptySourceRoot, {"one.js", "two.js"});
};
/// "test that the source root is reflected in a mapping's source field" from
/// https://github.com/mozilla/source-map/blob/master/test/test-source-map-consumer.js
TEST(SourceMap, SourceRoot) {
SourceErrorManager sm;
SimpleDiagHandlerRAII diagHandler(sm);
std::unique_ptr<SourceMap> sourceMap = SourceMapParser::parse(TestMap, sm);
llvh::Optional<SourceMapTextLocation> locOpt =
sourceMap->getLocationForAddress(2, 2);
EXPECT_TRUE(locOpt.hasValue());
EXPECT_EQ(locOpt.getValue().fileName, "/the/root/two.js");
locOpt = sourceMap->getLocationForAddress(1, 2);
EXPECT_TRUE(locOpt.hasValue());
EXPECT_EQ(locOpt.getValue().fileName, "/the/root/one.js");
std::unique_ptr<SourceMap> sourceMap2 =
SourceMapParser::parse(TestMapNoSourceRoot, sm);
locOpt = sourceMap2->getLocationForAddress(2, 2);
EXPECT_TRUE(locOpt.hasValue());
EXPECT_EQ(locOpt.getValue().fileName, "two.js");
locOpt = sourceMap2->getLocationForAddress(1, 2);
EXPECT_TRUE(locOpt.hasValue());
EXPECT_EQ(locOpt.getValue().fileName, "one.js");
std::unique_ptr<SourceMap> sourceMap3 =
SourceMapParser::parse(TestMapEmptySourceRoot, sm);
locOpt = sourceMap3->getLocationForAddress(2, 2);
EXPECT_TRUE(locOpt.hasValue());
EXPECT_EQ(locOpt.getValue().fileName, "two.js");
locOpt = sourceMap3->getLocationForAddress(1, 2);
EXPECT_TRUE(locOpt.hasValue());
EXPECT_EQ(locOpt.getValue().fileName, "one.js");
};
/// "test mapping tokens back exactly" from
/// https://github.com/mozilla/source-map/blob/master/test/test-source-map-consumer.js
TEST(SourceMap, ExactMappings) {
SourceErrorManager sm;
SimpleDiagHandlerRAII diagHandler(sm);
std::unique_ptr<SourceMap> sourceMap = SourceMapParser::parse(TestMap, sm);
std::vector<std::string> sources = {"/the/root/one.js", "/the/root/two.js"};
int generatedLine = 1;
int sourceIndex = 0;
verifySegment(*sourceMap, generatedLine, sources, loc(1, sourceIndex, 1, 1));
verifySegment(*sourceMap, generatedLine, sources, loc(5, sourceIndex, 1, 5));
verifySegment(*sourceMap, generatedLine, sources, loc(9, sourceIndex, 1, 11));
verifySegment(
*sourceMap, generatedLine, sources, loc(18, sourceIndex, 1, 21));
verifySegment(*sourceMap, generatedLine, sources, loc(21, sourceIndex, 2, 3));
verifySegment(
*sourceMap, generatedLine, sources, loc(28, sourceIndex, 2, 10));
verifySegment(
*sourceMap, generatedLine, sources, loc(32, sourceIndex, 2, 14));
generatedLine = 2;
sourceIndex = 1;
verifySegment(*sourceMap, generatedLine, sources, loc(1, sourceIndex, 1, 1));
verifySegment(*sourceMap, generatedLine, sources, loc(5, sourceIndex, 1, 5));
verifySegment(*sourceMap, generatedLine, sources, loc(9, sourceIndex, 1, 11));
verifySegment(
*sourceMap, generatedLine, sources, loc(18, sourceIndex, 1, 21));
verifySegment(*sourceMap, generatedLine, sources, loc(21, sourceIndex, 2, 3));
verifySegment(
*sourceMap, generatedLine, sources, loc(28, sourceIndex, 2, 10));
};
/// "test mapping tokens fuzzy" from
/// https://github.com/mozilla/source-map/blob/master/test/test-source-map-consumer.js
TEST(SourceMap, FuzzyMappings) {
SourceErrorManager sm;
SimpleDiagHandlerRAII diagHandler(sm);
std::unique_ptr<SourceMap> sourceMap = SourceMapParser::parse(TestMap, sm);
std::vector<std::string> sources = {"/the/root/one.js", "/the/root/two.js"};
verifySegment(*sourceMap, 1, sources, loc(20, 0, 1, 21));
verifySegment(*sourceMap, 1, sources, loc(30, 0, 2, 10));
verifySegment(*sourceMap, 2, sources, loc(12, 1, 1, 11));
};
/// Test to make sure we can parse mappings with no represented location
TEST(SourceMap, NoRepresentedLocation) {
SourceErrorManager sm;
SimpleDiagHandlerRAII diagHandler(sm);
std::unique_ptr<SourceMap> sourceMap = SourceMapParser::parse(
R"#({
"version": 3,
"sources": ["a.js", "b.js"],
"mappings": "CACC,E,G;A,A,CCCC"
})#",
sm);
std::vector<std::string> sources = {"a.js", "b.js"};
int generatedLine = 1;
int sourceIndex = 0;
verifySegment(*sourceMap, generatedLine, sources, loc(1, sourceIndex, 2, 1));
verifySegment(*sourceMap, generatedLine, sources, loc(3));
verifySegment(*sourceMap, generatedLine, sources, loc(6));
generatedLine = 2;
sourceIndex = 1;
verifySegment(*sourceMap, generatedLine, sources, loc(0));
verifySegment(*sourceMap, generatedLine, sources, loc(0));
verifySegment(*sourceMap, generatedLine, sources, loc(1, sourceIndex, 3, 2));
};
/// Test source map merging behavior.
///
/// Suppose we're compiling file1 and file2 and we have input source maps for
/// both files:
///
/// (I)
/// file1 -> file1orig
/// file2 -> file2orig
///
/// If we generate mappings to file1 and file2,
///
/// (II)
/// output -> file1 + file2
///
/// after merging them with (I) we expect to see:
///
/// (III)
/// output -> file1orig + file2orig (+ file1 + file2)
///
/// No mappings to file1 and file2 may remain in (III) because we consider
/// those to be intermediate artifacts (based on the fact that they have source
/// maps).
TEST(SourceMap, MergedWithInputSourceMaps) {
static const char file1MapJson[] =
R"#({
"version": 3,
"sources": ["file1orig"],
"mappings": "CAAA"
})#";
static const char file2MapJson[] =
R"#({
"version": 3,
"sourceRoot": "/foo/",
"sources": ["file2orig"],
"mappings": "CACA,KACC"
})#";
std::vector<std::unique_ptr<SourceMap>> inputSourceMaps{};
SourceErrorManager sm;
SimpleDiagHandlerRAII diagHandler(sm);
inputSourceMaps.push_back(SourceMapParser::parse(file1MapJson, sm));
inputSourceMaps.push_back(SourceMapParser::parse(file2MapJson, sm));
std::vector<std::string> sources = {"file1", "file2"};
SourceMap::SegmentList segments = {
loc(0, 0, 1, 0), // addr 1:0 -> file1:1:0 (unmapped in file1orig)
loc(2, 0, 1, 1), // addr 1:2 -> file1:1:1 -> file1orig:1:0
loc(3, 0, 1, 4), // addr 1:3 -> file1:1:4 -> file1orig:1:0
loc(4, 0, 2, 0), // addr 1:4 -> file1:2:0 (unmapped in file1orig)
loc(5), // addr 1:5 -> unmapped
loc(6, 1, 1, 0), // addr 1:6 -> file2:1:0 (unmapped in file2orig)
loc(7, 1, 1, 1), // addr 1:7 -> file2:1:1 -> file2orig:2:0
loc(8, 1, 1, 4), // addr 1:8 -> file2:1:4 -> file2orig:2:0
loc(9, 1, 1, 6), // addr 1:9 -> file2:1:6 -> file2orig:3:1
};
std::vector<std::string> expectedSources = {"file1orig", "/foo/file2orig"};
SourceMap::SegmentList expectedSegments = {
loc(0), // addr 1:0(-> file1:1:0)-> unmapped
loc(2, 0, 1, 0), // addr 1:2 -> file1:1:1 -> file1orig:1:0
loc(3, 0, 1, 0), // addr 1:3 -> file1:1:4 -> file1orig:1:0
loc(4), // addr 1:4(-> file1:2:0)-> unmapped
loc(5), // addr 1:5 -> unmapped
loc(6), // addr 1:6(-> file2:1:0)-> unmapped
loc(7, 1, 2, 0), // addr 1:7 -> file2:1:1 -> file2orig:2:0
loc(8, 1, 2, 0), // addr 1:8 -> file2:1:4 -> file2orig:2:0
loc(9, 1, 3, 1), // addr 1:9 -> file2:1:6 -> file2orig:3:1
};
SourceMapGenerator gen;
for (const auto &source : sources) {
gen.addSource(source);
}
gen.setInputSourceMaps(std::move(inputSourceMaps));
gen.addMappingsLine(segments, 0);
std::string storage;
llvh::raw_string_ostream OS(storage);
gen.outputAsJSON(OS);
EXPECT_EQ(
OS.str(),
R"#({"version":3,"sources":["file1orig","\/foo\/file2orig"],)#"
R"#("mappings":"A,EAAA,CAAA,C,C,C,CCCA,CAAA,CACC;"})#");
std::unique_ptr<SourceMap> sourceMap = SourceMapParser::parse(storage, sm);
for (uint32_t i = 0; i < expectedSegments.size(); ++i) {
verifySegment(
*sourceMap, /*generatedLine*/ 1, expectedSources, expectedSegments[i]);
}
}
class SimpleJSONParser {
std::shared_ptr<JSLexer::Allocator> alloc_;
JSONFactory factory_;
SourceErrorManager sm_;
JSONParser parser_;
JSONValue *value_;
public:
const JSONValue *getValue() const {
return value_;
}
JSONSharedValue getSharedValue() const {
return JSONSharedValue(getValue(), alloc_);
}
SimpleJSONParser(llvh::StringRef input)
: alloc_(std::make_shared<JSLexer::Allocator>()),
factory_(*alloc_),
parser_(factory_, input, sm_) {
value_ = parser_.parse().getValue();
}
};
TEST(SourceMap, PropagateFbMetadataFromInputs) {
SourceMapGenerator gen;
static const char file1MapJson[] =
R"#({
"version": 3,
"sources": ["file1orig"],
"x_facebook_sources": [[{
"names": ["FILE1ORIG"],
"mappings": "AAA,AAA"
}]],
"mappings": "CAAA"
})#";
static const char file2MapJson[] =
R"#({
"version": 3,
"sourceRoot": "/foo/",
"sources": ["file2orig"],
"x_facebook_sources": [[{
"names": ["FILE2ORIG"],
"mappings": "AAA"
}]],
"mappings": "CACA,KACC"
})#";
SimpleJSONParser file2MetadataJson(
R"#([{
"names": ["FILE2"],
"mappings": "AAA"
}, 42])#");
std::vector<std::unique_ptr<SourceMap>> inputSourceMaps{};
SourceErrorManager sm;
SimpleDiagHandlerRAII diagHandler(sm);
inputSourceMaps.push_back(SourceMapParser::parse(file1MapJson, sm));
inputSourceMaps.push_back(SourceMapParser::parse(file2MapJson, sm));
SourceMap::SegmentList segments = {
loc(0, 0, 1, 0), // addr 1:0 -> file1:1:0 (unmapped in file1orig)
loc(2, 0, 1, 1), // addr 1:2 -> file1:1:1 -> file1orig:1:0
loc(3, 0, 1, 4), // addr 1:3 -> file1:1:4 -> file1orig:1:0
loc(4, 0, 2, 0), // addr 1:4 -> file1:2:0 (unmapped in file1orig)
loc(5), // addr 1:5 -> unmapped
loc(6, 1, 1, 0), // addr 1:6 -> file2:1:0 (unmapped in file2orig)
loc(7, 1, 1, 1), // addr 1:7 -> file2:1:1 -> file2orig:2:0
loc(8, 1, 1, 4), // addr 1:8 -> file2:1:4 -> file2orig:2:0
loc(9, 1, 1, 6), // addr 1:9 -> file2:1:6 -> file2orig:3:1
};
gen.addSource("file1", llvh::None);
gen.addSource("file2", file2MetadataJson.getSharedValue());
gen.setInputSourceMaps(std::move(inputSourceMaps));
gen.addMappingsLine(segments, 0);
std::string storage;
llvh::raw_string_ostream OS(storage);
gen.outputAsJSON(OS);
EXPECT_EQ(
OS.str(),
R"#({"version":3,"sources":["file1orig","\/foo\/file2orig"],)#"
R"#("x_facebook_sources":[[{"mappings":"AAA,AAA","names":["FILE1ORIG"]}],)#"
R"#([{"mappings":"AAA","names":["FILE2ORIG"]}]],)#"
R"#("mappings":"A,EAAA,CAAA,C,C,C,CCCA,CAAA,CACC;"})#");
}
/// Test that we output the x_facebook_sources field if we have data for it
TEST(SourceMap, GenerateWithFbMetadata) {
SourceMapGenerator gen;
SimpleJSONParser file2MetadataJson(
R"#([{
"names": ["<global>"],
"mappings": "AAA"
}, 42])#");
SourceMap::SegmentList segments = {
loc(0, 0, 1, 0), // addr 1:0 -> file1:1:0
loc(1, 1, 1, 0), // addr 1:1 -> file2:1:0
};
gen.addSource("file1", llvh::None);
gen.addSource("file2", file2MetadataJson.getSharedValue());
gen.addMappingsLine(segments, 0);
std::string storage;
llvh::raw_string_ostream OS(storage);
gen.outputAsJSON(OS);
EXPECT_EQ(
OS.str(),
R"#({"version":3,"sources":["file1","file2"],"x_facebook_sources":)#"
R"#([null,[{"mappings":"AAA","names":["<global>"]},42]],"mappings":)#"
R"#("AAAA,CCAA;"})#");
}
/// Test to make sure we can properly parse empty lines.
TEST(SourceMap, EmptyLines) {
SourceErrorManager sm;
SimpleDiagHandlerRAII diagHandler(sm);
std::unique_ptr<SourceMap> sourceMap =
SourceMapParser::parse(TestMapEmptyLines, sm);
std::vector<std::string> sources = {"one.js", "two.js"};
int generatedLine = 3;
int sourceIndex = 0;
verifySegment(*sourceMap, generatedLine, sources, loc(1, sourceIndex, 1, 1));
verifySegment(*sourceMap, generatedLine, sources, loc(5, sourceIndex, 1, 5));
verifySegment(*sourceMap, generatedLine, sources, loc(9, sourceIndex, 1, 11));
verifySegment(
*sourceMap, generatedLine, sources, loc(18, sourceIndex, 1, 21));
verifySegment(*sourceMap, generatedLine, sources, loc(21, sourceIndex, 2, 3));
verifySegment(
*sourceMap, generatedLine, sources, loc(28, sourceIndex, 2, 10));
verifySegment(
*sourceMap, generatedLine, sources, loc(32, sourceIndex, 2, 14));
generatedLine = 4;
sourceIndex = 1;
verifySegment(*sourceMap, generatedLine, sources, loc(1, sourceIndex, 1, 1));
verifySegment(*sourceMap, generatedLine, sources, loc(5, sourceIndex, 1, 5));
verifySegment(*sourceMap, generatedLine, sources, loc(9, sourceIndex, 1, 11));
verifySegment(
*sourceMap, generatedLine, sources, loc(18, sourceIndex, 1, 21));
verifySegment(*sourceMap, generatedLine, sources, loc(21, sourceIndex, 2, 3));
verifySegment(
*sourceMap, generatedLine, sources, loc(28, sourceIndex, 2, 10));
}
TEST(SourceMap, VLQRandos) {
// clang-format off
const std::vector<int32_t> inputs = {0, 1, -1, 2, -2, 5298, -23498,
INT_MIN/2, INT_MAX/2, INT_MIN+1,
INT_MAX-1, INT_MIN, INT_MAX, INT_MIN+1,
0, 100, 0, 100, 42};
// clang-format on
// Encode every input into the string.
std::string storage;
llvh::raw_string_ostream OS(storage);
for (int32_t x : inputs)
base64vlq::encode(OS, x);
OS.flush();
// Decode from the string.
std::vector<int32_t> outputs;
const char *begin = storage.c_str();
const char *end = begin + storage.size();
while (auto decoded = base64vlq::decode(begin, end))
outputs.push_back(*decoded);
EXPECT_EQ(outputs, inputs);
}
TEST(SourceMap, VLQDecodeInvalids) {
auto decode = [](const char *s) -> OptValue<int32_t> {
return base64vlq::decode(s, s + strlen(s));
};
auto opt = [](int32_t x) -> OptValue<int32_t> { return x; };
const OptValue<int32_t> none{llvh::None};
EXPECT_EQ(opt(INT32_MAX), decode("+/////D")); // 2**31-1
EXPECT_EQ(opt(-INT32_MAX), decode("//////D")); // -2**31+1
// note http://www.murzwin.com/base64vlq.html gets this wrong!
EXPECT_EQ(opt(INT32_MIN), decode("hgggggE"));
EXPECT_EQ(none, decode("jgggggE")); // -2**31-1.
EXPECT_EQ(opt(1 << 30), decode("ggggggC")); // 2**30
EXPECT_EQ(none, decode("gggggggC")); // Something very big and positive.
EXPECT_EQ(none, decode("hggggggC")); // Something very big and negative.
EXPECT_EQ(none, decode(""));
EXPECT_EQ(none, decode("!"));
EXPECT_EQ(opt(1024), decode("ggC"));
EXPECT_EQ(none, decode(" ggC"));
EXPECT_EQ(none, decode("//")); // too many continuation bits.
EXPECT_EQ(none, decode("67")); // too many continuation bits.
EXPECT_EQ(opt(0), decode("A")); // plain old zero
// SourceMap encoding is one's complement, so this represents an integer
// "negative zero." Treat it as zero.
EXPECT_EQ(opt(0), decode("B"));
}
} // end anonymous namespace