1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/tools/profile_reset/jtl_compiler.h"
6
7#include <string>
8
9#include "base/values.h"
10#include "chrome/browser/profile_resetter/jtl_foundation.h"
11#include "chrome/browser/profile_resetter/jtl_instructions.h"
12#include "testing/gmock/include/gmock/gmock.h"
13#include "testing/gtest/include/gtest/gtest.h"
14
15namespace {
16
17const char kTestHashSeed[] = "test-hash-seed";
18
19// Helpers -------------------------------------------------------------------
20
21std::string GetHash(const std::string& input) {
22  return jtl_foundation::Hasher(kTestHashSeed).GetHash(input);
23}
24
25static std::string EncodeUint32(uint32 value) {
26  std::string bytecode;
27  for (int i = 0; i < 4; ++i) {
28    bytecode.push_back(static_cast<char>(value & 0xFFu));
29    value >>= 8;
30  }
31  return bytecode;
32}
33
34// Tests ---------------------------------------------------------------------
35
36// Note: Parsing and parsing-related errors are unit-tested separately in more
37// detail in "jtl_parser_unittest.cc". Here, most of  the time, we assume that
38// creating the parse tree works.
39
40TEST(JtlCompiler, CompileSingleInstructions) {
41  struct TestCase {
42    std::string source_code;
43    std::string expected_bytecode;
44  } cases[] = {
45        {"go(\"foo\").", OP_NAVIGATE(GetHash("foo"))},
46        {"go(\"has whitespace\t\").", OP_NAVIGATE(GetHash("has whitespace\t"))},
47        {"any.", OP_NAVIGATE_ANY},
48        {"back.", OP_NAVIGATE_BACK},
49        {"store_bool(\"name\", true).",
50         OP_STORE_BOOL(GetHash("name"), VALUE_TRUE)},
51        {"compare_stored_bool(\"name\", true, false).",
52         OP_COMPARE_STORED_BOOL(GetHash("name"), VALUE_TRUE, VALUE_FALSE)},
53        {"store_hash(\"name\", \"" + GetHash("value") + "\").",
54         OP_STORE_HASH(GetHash("name"), GetHash("value"))},
55        {"store_hashed(\"name\", \"value\").",
56         OP_STORE_HASH(GetHash("name"), GetHash("value"))},
57        {"store_node_bool(\"name\").", OP_STORE_NODE_BOOL(GetHash("name"))},
58        {"store_node_hash(\"name\").", OP_STORE_NODE_HASH(GetHash("name"))},
59        {"store_node_effective_sld_hash(\"name\").",
60         OP_STORE_NODE_EFFECTIVE_SLD_HASH(GetHash("name"))},
61        {"compare_stored_hashed(\"name\", \"value\", \"default\").",
62         OP_COMPARE_STORED_HASH(
63             GetHash("name"), GetHash("value"), GetHash("default"))},
64        {"compare_bool(false).", OP_COMPARE_NODE_BOOL(VALUE_FALSE)},
65        {"compare_bool(true).", OP_COMPARE_NODE_BOOL(VALUE_TRUE)},
66        {"compare_hashed(\"foo\").", OP_COMPARE_NODE_HASH(GetHash("foo"))},
67        {"compare_hashed_not(\"foo\").",
68         OP_COMPARE_NODE_HASH_NOT(GetHash("foo"))},
69        {"compare_to_stored_bool(\"name\").",
70         OP_COMPARE_NODE_TO_STORED_BOOL(GetHash("name"))},
71        {"compare_to_stored_hash(\"name\").",
72         OP_COMPARE_NODE_TO_STORED_HASH(GetHash("name"))},
73        {"compare_substring_hashed(\"pattern\").",
74         OP_COMPARE_NODE_SUBSTRING(
75             GetHash("pattern"), EncodeUint32(7), EncodeUint32(766))},
76        {"break.", OP_STOP_EXECUTING_SENTENCE},
77        {"break;", OP_STOP_EXECUTING_SENTENCE + OP_END_OF_SENTENCE}};
78
79  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
80    SCOPED_TRACE(cases[i].source_code);
81    std::string bytecode;
82    EXPECT_TRUE(JtlCompiler::Compile(
83        cases[i].source_code, kTestHashSeed, &bytecode, NULL));
84    EXPECT_EQ(cases[i].expected_bytecode, bytecode);
85  }
86}
87
88TEST(JtlCompiler, CompileEntireProgram) {
89  const char kSourceCode[] =
90      "// Store \"x\"=true if path is found.\n"
91      "go(\"foo\").go(\"bar\").store_bool(\"x\", true);\n"
92      "// ...\n"
93      "// Store \"y\"=\"1\" if above value is set.\n"
94      "compare_stored_bool(\"x\", true, false).store_hashed(\"y\", \"1\");\n";
95
96  std::string expected_bytecode =
97      OP_NAVIGATE(GetHash("foo")) +
98      OP_NAVIGATE(GetHash("bar")) +
99      OP_STORE_BOOL(GetHash("x"), VALUE_TRUE) + OP_END_OF_SENTENCE +
100      OP_COMPARE_STORED_BOOL(GetHash("x"), VALUE_TRUE, VALUE_FALSE) +
101      OP_STORE_HASH(GetHash("y"), GetHash("1")) + OP_END_OF_SENTENCE;
102
103  std::string bytecode;
104  EXPECT_TRUE(
105      JtlCompiler::Compile(kSourceCode, kTestHashSeed, &bytecode, NULL));
106  EXPECT_EQ(expected_bytecode, bytecode);
107}
108
109TEST(JtlCompiler, InvalidOperationName) {
110  const char kSourceCode[] = "any()\n.\nnon_existent_instruction\n(\n)\n;\n";
111
112  std::string bytecode;
113  JtlCompiler::CompileError error;
114  EXPECT_FALSE(
115      JtlCompiler::Compile(kSourceCode, kTestHashSeed, &bytecode, &error));
116  EXPECT_THAT(error.context, testing::StartsWith("non_existent_instruction"));
117  EXPECT_EQ(2u, error.line_number);
118  EXPECT_EQ(JtlCompiler::CompileError::INVALID_OPERATION_NAME,
119            error.error_code);
120}
121
122TEST(JtlCompiler, InvalidArgumentsCount) {
123  const char* kSourceCodes[] = {
124      "any().\nstore_bool(\"name\", true, \"superfluous argument\");\n",
125      "any().\nstore_bool(\"name\");"};  // missing argument
126
127  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kSourceCodes); ++i) {
128    SCOPED_TRACE(kSourceCodes[i]);
129    std::string bytecode;
130    JtlCompiler::CompileError error;
131    EXPECT_FALSE(JtlCompiler::Compile(
132        kSourceCodes[i], kTestHashSeed, &bytecode, &error));
133    EXPECT_THAT(error.context, testing::StartsWith("store_bool"));
134    EXPECT_EQ(1u, error.line_number);
135    EXPECT_EQ(JtlCompiler::CompileError::INVALID_ARGUMENT_COUNT,
136              error.error_code);
137  }
138}
139
140TEST(JtlCompiler, InvalidArgumentType) {
141  struct TestCase {
142    std::string expected_context_prefix;
143    std::string source_code;
144  } cases[] = {
145        {"compare_bool", "any()\n.\ncompare_bool(\"foo\");"},
146        {"compare_bool",
147         "any()\n.\ncompare_bool(\"01234567890123456789012345678901\");"},
148        {"compare_hashed", "any()\n.\ncompare_hashed(false);"},
149        {"store_hash", "any()\n.\nstore_hash(\"name\", false);"},
150        {"store_hash", "any()\n.\nstore_hash(\"name\", \"foo\");"},
151        {"compare_stored_bool",
152         "any()\n.\ncompare_stored_bool(\"name\", \"need a bool\", false);"},
153        {"compare_stored_bool",
154         "any()\n.\ncompare_stored_bool(\"name\", false, \"need a bool\");"},
155        {"compare_substring_hashed",
156         "any()\n.\ncompare_substring_hashed(true);"}};
157
158  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
159    SCOPED_TRACE(cases[i].source_code);
160    std::string bytecode;
161    JtlCompiler::CompileError error;
162    EXPECT_FALSE(JtlCompiler::Compile(
163        cases[i].source_code, kTestHashSeed, &bytecode, &error));
164    EXPECT_THAT(error.context,
165                testing::StartsWith(cases[i].expected_context_prefix));
166    EXPECT_EQ(2u, error.line_number);
167    EXPECT_EQ(JtlCompiler::CompileError::INVALID_ARGUMENT_TYPE,
168              error.error_code);
169  }
170}
171
172TEST(JtlCompiler, InvalidArgumentValue) {
173  struct TestCase {
174    std::string expected_context_prefix;
175    std::string source_code;
176  } cases[] = {
177        {"compare_substring_hashed", "compare_substring_hashed(\"\");"}};
178
179  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
180    SCOPED_TRACE(cases[i].source_code);
181    std::string bytecode;
182    JtlCompiler::CompileError error;
183    EXPECT_FALSE(JtlCompiler::Compile(
184        cases[i].source_code, kTestHashSeed, &bytecode, &error));
185    EXPECT_THAT(error.context,
186                testing::StartsWith(cases[i].expected_context_prefix));
187    EXPECT_EQ(0u, error.line_number);
188    EXPECT_EQ(JtlCompiler::CompileError::INVALID_ARGUMENT_VALUE,
189              error.error_code);
190  }
191}
192
193TEST(JtlCompiler, MistmatchedDoubleQuotes) {
194  const char kSourceCode[] = "any().\ngo(\"ok\", \"stray quote).break();";
195
196  std::string bytecode;
197  JtlCompiler::CompileError error;
198  EXPECT_FALSE(
199      JtlCompiler::Compile(kSourceCode, kTestHashSeed, &bytecode, &error));
200  EXPECT_EQ(1u, error.line_number);
201  EXPECT_EQ(JtlCompiler::CompileError::MISMATCHED_DOUBLE_QUOTES,
202            error.error_code);
203}
204
205TEST(JtlCompiler, ParsingError) {
206  const char kSourceCode[] = "any().\ngo()missing_separator();";
207
208  std::string bytecode;
209  JtlCompiler::CompileError error;
210  EXPECT_FALSE(
211      JtlCompiler::Compile(kSourceCode, kTestHashSeed, &bytecode, &error));
212  EXPECT_THAT(error.context, testing::StartsWith("go"));
213  EXPECT_EQ(1u, error.line_number);
214  EXPECT_EQ(JtlCompiler::CompileError::PARSING_ERROR, error.error_code);
215}
216
217}  // namespace
218