| // Copyright 2017 The Fuchsia Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| #import "tools/test_app/test_app.h" |
| |
| #include <map> |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include "config/config_text_parser.h" |
| #include "encoder/project_context.h" |
| #include "gflags/gflags.h" |
| #include "glog/logging.h" |
| #include "third_party/googletest/googletest/include/gtest/gtest.h" |
| |
| namespace cobalt { |
| |
| DECLARE_uint32(num_clients); |
| DECLARE_string(values); |
| |
| using config::EncodingRegistry; |
| using config::MetricRegistry; |
| using encoder::EnvelopeMaker; |
| using encoder::ProjectContext; |
| using encoder::ShufflerClientInterface; |
| using encoder::SystemData; |
| |
| namespace { |
| const uint32_t kCustomerId = 1; |
| const uint32_t kProjectId = 1; |
| |
| const char* const kMetricConfigText = R"( |
| # Metric one string part named url. |
| element { |
| customer_id: 1 |
| project_id: 1 |
| id: 1 |
| name: "Fuchsia Popular URLs" |
| description: "This is a fictional metric used for the development of Cobalt." |
| time_zone_policy: LOCAL |
| parts { |
| key: "url" |
| value { |
| description: "A URL." |
| data_type: STRING |
| } |
| } |
| } |
| |
| # Metric 2 has one integer part named hour. |
| element { |
| customer_id: 1 |
| project_id: 1 |
| id: 2 |
| name: "Fuchsia Usage by Hour" |
| description: "This is a fictional metric used for the development of Cobalt." |
| time_zone_policy: LOCAL |
| parts { |
| key: "hour" |
| value { |
| description: "An integer from 0 to 23 representing the hour of the day." |
| data_type: INT |
| } |
| } |
| } |
| |
| # Metric 3 has one string part named "fruit" and one int part named "rating". |
| element { |
| customer_id: 1 |
| project_id: 1 |
| id: 3 |
| name: "Fuchsia Fruit Consumption and Rating" |
| description: "This is a fictional metric used for the development of Cobalt." |
| time_zone_policy: LOCAL |
| parts { |
| key: "fruit" |
| value { |
| description: "The name of a fruit that was consumed." |
| } |
| } |
| parts { |
| key: "rating" |
| value { |
| description: "An integer from 0 to 10" |
| data_type: INT |
| } |
| } |
| } |
| |
| # Metric 4 has one INDEX part named event. |
| element { |
| customer_id: 1 |
| project_id: 1 |
| id: 4 |
| name: "Fuchsia Rare Events" |
| description: "This is a fictional metric used for the development of Cobalt." |
| time_zone_policy: LOCAL |
| parts { |
| key: "event" |
| value { |
| description: "The index of the event type." |
| data_type: INDEX |
| } |
| } |
| } |
| |
| )"; |
| |
| const char* const kEncodingConfigText = R"( |
| # EncodingConfig 1 is Forculus, 20. |
| element { |
| customer_id: 1 |
| project_id: 1 |
| id: 1 |
| forculus { |
| threshold: 20 |
| epoch_type: DAY |
| } |
| } |
| |
| # EncodingConfig 2 is Basic RAPPOR with integer categories [0, 23] |
| element { |
| customer_id: 1 |
| project_id: 1 |
| id: 2 |
| basic_rappor { |
| prob_0_becomes_1: 0.1 |
| prob_1_stays_1: 0.9 |
| int_range_categories: { |
| first: 0 |
| last: 23 |
| } |
| } |
| } |
| |
| # EncodingConfig 3 is Basic RAPPOR with String categories for fruit types. |
| element { |
| customer_id: 1 |
| project_id: 1 |
| id: 3 |
| basic_rappor { |
| prob_0_becomes_1: 0.01 |
| prob_1_stays_1: 0.99 |
| string_categories: { |
| category: "apple" |
| category: "banana" |
| category: "cantaloupe" |
| } |
| } |
| } |
| |
| # EncodingConfig 4 is Basic RAPPOR with integer categories in [0, 10] |
| element { |
| customer_id: 1 |
| project_id: 1 |
| id: 4 |
| basic_rappor { |
| prob_0_becomes_1: 0.2 |
| prob_1_stays_1: 0.8 |
| int_range_categories: { |
| first: 0 |
| last: 10 |
| } |
| } |
| } |
| |
| # EncodingConfig 5 is Basic RAPPOR with 100 indexed categories |
| element { |
| customer_id: 1 |
| project_id: 1 |
| id: 5 |
| basic_rappor { |
| prob_0_becomes_1: 0.2 |
| prob_1_stays_1: 0.8 |
| indexed_categories: { |
| num_categories: 100 |
| } |
| } |
| } |
| |
| )"; |
| |
| // Returns a ProjectContext obtained by parsing the above configuration text |
| // strings. |
| std::shared_ptr<ProjectContext> GetTestProject() { |
| // Parse the metric config string |
| auto metric_parse_result = |
| config::FromString<RegisteredMetrics>(kMetricConfigText, nullptr); |
| EXPECT_EQ(config::kOK, metric_parse_result.second); |
| std::shared_ptr<MetricRegistry> metric_registry( |
| metric_parse_result.first.release()); |
| |
| // Parse the encoding config string |
| auto encoding_parse_result = |
| config::FromString<RegisteredEncodings>(kEncodingConfigText, nullptr); |
| EXPECT_EQ(config::kOK, encoding_parse_result.second); |
| std::shared_ptr<EncodingRegistry> encoding_registry( |
| encoding_parse_result.first.release()); |
| |
| return std::shared_ptr<ProjectContext>(new ProjectContext( |
| kCustomerId, kProjectId, metric_registry, encoding_registry)); |
| } |
| |
| // An implementation of AnalyzerClientInterface that just stores the |
| // Envelope for inspection by a test. |
| class FakeAnalyzerClient : public AnalyzerClientInterface { |
| public: |
| virtual ~FakeAnalyzerClient() = default; |
| |
| void SendToAnalyzer(const Envelope& e) override { |
| // Copy the EnvelopeMaker's Envelope. |
| envelope = e; |
| } |
| Envelope envelope; |
| }; |
| |
| // An implementation of ShufflerClientInterface that just stores the |
| // Envelope for inspection by a test. |
| class FakeShufflerClient : public ShufflerClientInterface { |
| public: |
| FakeShufflerClient() : decrypter_("") {} |
| |
| grpc::Status SendToShuffler(const EncryptedMessage& encrypted_message, |
| grpc::ClientContext* context = nullptr) override { |
| EXPECT_TRUE(decrypter_.DecryptMessage(encrypted_message, &envelope)); |
| return grpc::Status::OK; |
| } |
| Envelope envelope; |
| |
| private: |
| cobalt::util::MessageDecrypter decrypter_; |
| }; |
| |
| // Parses the |ciphertext| field of the given EncryptedMessage assuming |
| // it contains the unencrypted serialized bytes of an Observation. |
| Observation ParseUnencryptedObservation(const EncryptedMessage& em) { |
| Observation observation; |
| EXPECT_EQ(EncryptedMessage::NONE, em.scheme()); |
| observation.ParseFromString(em.ciphertext()); |
| return observation; |
| } |
| |
| } // namespace |
| |
| // Tests of the TestApp class. |
| class TestAppTest : public ::testing::Test { |
| public: |
| TestAppTest() |
| : fake_analyzer_client_(new FakeAnalyzerClient()), |
| fake_shuffler_client_(new FakeShufflerClient()), |
| test_app_(GetTestProject(), fake_analyzer_client_, |
| fake_shuffler_client_, std::unique_ptr<SystemData>(), "", |
| EncryptedMessage::NONE, "", EncryptedMessage::NONE, |
| &output_stream_) {} |
| |
| protected: |
| // Clears the contents of the TestApp's output stream and returns the |
| // contents prior to clearing. |
| std::string ClearOutput() { |
| std::string s = output_stream_.str(); |
| output_stream_.str(""); |
| return s; |
| } |
| |
| // Does the current contents of the TestApp's output stream contain the |
| // given text. |
| bool OutputContains(const std::string text) { |
| return std::string::npos != output_stream_.str().find(text); |
| } |
| |
| // Is the TestApp's output stream curently empty? |
| bool NoOutput() { return output_stream_.str().empty(); } |
| |
| std::shared_ptr<FakeAnalyzerClient> fake_analyzer_client_; |
| std::shared_ptr<FakeShufflerClient> fake_shuffler_client_; |
| |
| // The output stream that the TestApp has been given. |
| std::ostringstream output_stream_; |
| |
| // The TestApp under test. |
| TestApp test_app_; |
| }; |
| |
| ////////////////////////////////////// |
| // Tests of interactive mode. |
| ///////////////////////////////////// |
| |
| // Tests processing a bad command line. |
| TEST_F(TestAppTest, ProcessCommandLineBad) { |
| EXPECT_TRUE(test_app_.ProcessCommandLine("this is not a command")); |
| EXPECT_TRUE(OutputContains("Unrecognized command: this")); |
| } |
| |
| // Tests processing the "help" command |
| TEST_F(TestAppTest, ProcessCommandLineHelp) { |
| EXPECT_TRUE(test_app_.ProcessCommandLine("help")); |
| // We don't want to test the actual output too rigorously because that would |
| // be a very fragile test. Just doing a sanity test. |
| EXPECT_TRUE(OutputContains("Print this help message.")); |
| EXPECT_TRUE( |
| OutputContains("Encode <num> independent copies of the string " |
| "or integer value <val>, or index <n> if")); |
| } |
| |
| // Tests processing a bad set command line. |
| TEST_F(TestAppTest, ProcessCommandLineSetBad) { |
| EXPECT_TRUE(test_app_.ProcessCommandLine("set")); |
| EXPECT_TRUE(OutputContains("Malformed set command.")); |
| ClearOutput(); |
| |
| EXPECT_TRUE(test_app_.ProcessCommandLine("set a b c")); |
| EXPECT_TRUE(OutputContains("Malformed set command.")); |
| ClearOutput(); |
| |
| EXPECT_TRUE(test_app_.ProcessCommandLine("set a b")); |
| EXPECT_TRUE(OutputContains("a is not a settable parameter")); |
| ClearOutput(); |
| |
| EXPECT_TRUE(test_app_.ProcessCommandLine("set metric b")); |
| EXPECT_TRUE(OutputContains("Expected positive integer instead of b.")); |
| ClearOutput(); |
| |
| EXPECT_TRUE(test_app_.ProcessCommandLine("set encoding b")); |
| EXPECT_TRUE(OutputContains("Expected positive integer instead of b.")); |
| ClearOutput(); |
| } |
| |
| // Tests processing the set and ls commands |
| TEST_F(TestAppTest, ProcessCommandLineSetAndLs) { |
| EXPECT_TRUE(test_app_.ProcessCommandLine("ls")); |
| EXPECT_TRUE(OutputContains("Metric ID: 1")); |
| EXPECT_TRUE(OutputContains("Encoding Config ID: 1")); |
| EXPECT_TRUE(OutputContains("Skip Shuffler: 0")); |
| ClearOutput(); |
| |
| EXPECT_TRUE(test_app_.ProcessCommandLine("set metric 2")); |
| EXPECT_TRUE(NoOutput()); |
| |
| EXPECT_TRUE(test_app_.ProcessCommandLine("ls")); |
| EXPECT_TRUE(OutputContains("Metric ID: 2")); |
| EXPECT_TRUE(OutputContains("Encoding Config ID: 1")); |
| EXPECT_TRUE(OutputContains("Skip Shuffler: 0")); |
| ClearOutput(); |
| |
| EXPECT_TRUE(test_app_.ProcessCommandLine("set encoding 2")); |
| EXPECT_TRUE(NoOutput()); |
| |
| EXPECT_TRUE(test_app_.ProcessCommandLine("ls")); |
| EXPECT_TRUE(OutputContains("Metric ID: 2")); |
| EXPECT_TRUE(OutputContains("Encoding Config ID: 2")); |
| EXPECT_TRUE(OutputContains("Skip Shuffler: 0")); |
| ClearOutput(); |
| |
| EXPECT_TRUE(test_app_.ProcessCommandLine("set skip_shuffler true")); |
| EXPECT_TRUE(NoOutput()); |
| |
| EXPECT_TRUE(test_app_.ProcessCommandLine("ls")); |
| EXPECT_TRUE(OutputContains("Metric ID: 2")); |
| EXPECT_TRUE(OutputContains("Encoding Config ID: 2")); |
| EXPECT_TRUE(OutputContains("Skip Shuffler: 1")); |
| ClearOutput(); |
| |
| EXPECT_TRUE(test_app_.ProcessCommandLine("set skip_shuffler false")); |
| EXPECT_TRUE(NoOutput()); |
| |
| EXPECT_TRUE(test_app_.ProcessCommandLine("ls")); |
| EXPECT_TRUE(OutputContains("Metric ID: 2")); |
| EXPECT_TRUE(OutputContains("Encoding Config ID: 2")); |
| EXPECT_TRUE(OutputContains("Skip Shuffler: 0")); |
| ClearOutput(); |
| } |
| |
| // Tests processing a bad show command line. |
| TEST_F(TestAppTest, ProcessCommandLineShowBad) { |
| EXPECT_TRUE(test_app_.ProcessCommandLine("show")); |
| EXPECT_TRUE(OutputContains("Expected 'show config'.")); |
| ClearOutput(); |
| |
| EXPECT_TRUE(test_app_.ProcessCommandLine("show confi")); |
| EXPECT_TRUE(OutputContains("Expected 'show config'.")); |
| ClearOutput(); |
| |
| EXPECT_TRUE(test_app_.ProcessCommandLine("show config foo")); |
| EXPECT_TRUE(OutputContains("Expected 'show config'.")); |
| ClearOutput(); |
| } |
| |
| // Tests processing the set and show config commands |
| TEST_F(TestAppTest, ProcessCommandLineSetAndShowConfig) { |
| EXPECT_TRUE(test_app_.ProcessCommandLine("show config")); |
| EXPECT_TRUE(OutputContains("Fuchsia Popular URLs")); |
| EXPECT_TRUE(OutputContains("One string part named \"url\": A URL.")); |
| EXPECT_TRUE(OutputContains("Forculus threshold=20")); |
| ClearOutput(); |
| |
| EXPECT_TRUE(test_app_.ProcessCommandLine("set metric 2")); |
| EXPECT_TRUE(NoOutput()); |
| |
| EXPECT_TRUE(test_app_.ProcessCommandLine("show config")); |
| EXPECT_TRUE(OutputContains("Fuchsia Usage by Hour")); |
| EXPECT_TRUE( |
| OutputContains("One int part named \"hour\": An integer from 0 to 23 " |
| "representing the hour of the day.")); |
| EXPECT_TRUE(OutputContains("Forculus threshold=20")); |
| ClearOutput(); |
| |
| EXPECT_TRUE(test_app_.ProcessCommandLine("set encoding 2")); |
| EXPECT_TRUE(NoOutput()); |
| |
| EXPECT_TRUE(test_app_.ProcessCommandLine("show config")); |
| EXPECT_TRUE(OutputContains("Fuchsia Usage by Hour")); |
| EXPECT_TRUE( |
| OutputContains("One int part named \"hour\": An integer from 0 to 23 " |
| "representing the hour of the day.")); |
| EXPECT_TRUE(OutputContains("Basic Rappor")); |
| EXPECT_TRUE(OutputContains("p=0.1, q=0.9")); |
| ClearOutput(); |
| |
| EXPECT_TRUE(test_app_.ProcessCommandLine("set metric 3")); |
| EXPECT_TRUE(test_app_.ProcessCommandLine("set encoding 3")); |
| EXPECT_TRUE(NoOutput()); |
| |
| EXPECT_TRUE(test_app_.ProcessCommandLine("show config")); |
| EXPECT_TRUE(OutputContains("Fuchsia Fruit Consumption and Rating")); |
| EXPECT_TRUE( |
| OutputContains("One int part named \"rating\": An integer from 0 to 10")); |
| EXPECT_TRUE( |
| OutputContains("One string part named \"fruit\": The name of a fruit " |
| "that was consumed.")); |
| EXPECT_TRUE(OutputContains("Basic Rappor")); |
| EXPECT_TRUE(OutputContains("p=0.01, q=0.99")) << output_stream_.str(); |
| ClearOutput(); |
| |
| EXPECT_TRUE(test_app_.ProcessCommandLine("set metric 4")); |
| EXPECT_TRUE(test_app_.ProcessCommandLine("set encoding 5")); |
| EXPECT_TRUE(NoOutput()); |
| |
| EXPECT_TRUE(test_app_.ProcessCommandLine("show config")); |
| EXPECT_TRUE(OutputContains("Fuchsia Rare Events")) << output_stream_.str(); |
| EXPECT_TRUE(OutputContains( |
| "One indexed part named \"event\": The index of the event type")); |
| EXPECT_TRUE(OutputContains("Basic Rappor")); |
| EXPECT_TRUE(OutputContains("p=0.2, q=0.8")); |
| EXPECT_TRUE(OutputContains("Categories:")); |
| EXPECT_TRUE(OutputContains("num_categories: 100")) << output_stream_.str(); |
| ClearOutput(); |
| |
| EXPECT_TRUE(test_app_.ProcessCommandLine("set metric 5")); |
| EXPECT_TRUE(test_app_.ProcessCommandLine("set encoding 6")); |
| EXPECT_TRUE(NoOutput()); |
| |
| EXPECT_TRUE(test_app_.ProcessCommandLine("show config")); |
| EXPECT_TRUE(OutputContains("There is no metric with id=5.")); |
| EXPECT_TRUE(OutputContains("There is no encoding config with id=6.")); |
| ClearOutput(); |
| } |
| |
| // Tests processing a bad encode command line. |
| TEST_F(TestAppTest, ProcessCommandLineEncodeBad) { |
| EXPECT_TRUE(test_app_.ProcessCommandLine("encode")); |
| EXPECT_TRUE(OutputContains("Malformed encode command.")); |
| ClearOutput(); |
| EXPECT_TRUE(test_app_.ProcessCommandLine("encode foo")); |
| EXPECT_TRUE(OutputContains("Malformed encode command.")); |
| |
| ClearOutput(); |
| EXPECT_TRUE(test_app_.ProcessCommandLine("encode foo bar")); |
| EXPECT_TRUE(OutputContains("Expected positive integer instead of foo.")); |
| |
| ClearOutput(); |
| |
| EXPECT_TRUE(test_app_.ProcessCommandLine("encode -1 bar")); |
| EXPECT_TRUE(OutputContains("<num> must be a positive integer: -1")); |
| |
| ClearOutput(); |
| EXPECT_TRUE(test_app_.ProcessCommandLine("encode 3.14 bar")); |
| EXPECT_TRUE(OutputContains("Expected positive integer instead of 3.14.")); |
| } |
| |
| // Tests processing a bad send command line. |
| TEST_F(TestAppTest, ProcessCommandLineSendBad) { |
| EXPECT_TRUE(test_app_.ProcessCommandLine("send foo")); |
| EXPECT_TRUE(OutputContains("The send command doesn't take any arguments.")); |
| } |
| |
| // Tests processing an encode and send operation. |
| TEST_F(TestAppTest, ProcessCommandLineEncodeAndSend) { |
| // The default is metric 1 encoding 1 which is Forculus with |
| // URLs. |
| EXPECT_TRUE(test_app_.ProcessCommandLine("encode 19 www.AAAA")); |
| EXPECT_TRUE(test_app_.ProcessCommandLine("encode 20 www.BBBB")); |
| EXPECT_TRUE(test_app_.ProcessCommandLine("send")); |
| EXPECT_TRUE(NoOutput()); |
| // The received envelope should contain 1 batch. |
| Envelope& envelope = fake_shuffler_client_->envelope; |
| ASSERT_EQ(1, envelope.batch_size()); |
| // That batch should contain 39 messages. |
| const ObservationBatch& batch = envelope.batch(0); |
| EXPECT_EQ(39, batch.encrypted_observation_size()); |
| // The metric ID should be the default value of 1. |
| EXPECT_EQ(1u, batch.meta_data().metric_id()); |
| // All of the Observations should have a single part named "url" that has an |
| // encoding config ID of the default value of 1. |
| for (const auto& encrypted_message : batch.encrypted_observation()) { |
| auto observation = ParseUnencryptedObservation(encrypted_message); |
| EXPECT_EQ(1, observation.parts_size()); |
| auto part = observation.parts().at("url"); |
| EXPECT_EQ(1u, part.encoding_config_id()); |
| } |
| |
| // Switch to metric 2 encoding 2 which is Basic RAPPOR with |
| // hours-of-the-day. |
| EXPECT_TRUE(test_app_.ProcessCommandLine("set encoding 2")); |
| EXPECT_TRUE(test_app_.ProcessCommandLine("set metric 2")); |
| |
| // Set skip_shuffler true. |
| EXPECT_TRUE(test_app_.ProcessCommandLine("set skip_shuffler true")); |
| EXPECT_TRUE(NoOutput()); |
| |
| EXPECT_TRUE(test_app_.ProcessCommandLine("encode 100 8")); |
| EXPECT_TRUE(test_app_.ProcessCommandLine("encode 200 9")); |
| EXPECT_TRUE(test_app_.ProcessCommandLine("send")); |
| EXPECT_TRUE(NoOutput()); |
| // The received envelope should contain 1 batch. |
| envelope = fake_analyzer_client_->envelope; |
| ASSERT_EQ(1, envelope.batch_size()); |
| // That batch should contain 300 messages. |
| const ObservationBatch& batch2 = envelope.batch(0); |
| EXPECT_EQ(300, batch2.encrypted_observation_size()); |
| // The metric ID should be 2. |
| EXPECT_EQ(2u, batch2.meta_data().metric_id()); |
| // All of the Observations should have a single part named "hour" that has an |
| // encoding config ID of 2 |
| for (const auto& encrypted_message : batch2.encrypted_observation()) { |
| auto observation = ParseUnencryptedObservation(encrypted_message); |
| EXPECT_EQ(1, observation.parts_size()); |
| auto part = observation.parts().at("hour"); |
| EXPECT_EQ(2u, part.encoding_config_id()); |
| } |
| |
| // Switch to metric 4 encoding 5 which is Basic RAPPOR with |
| // events encoded as indices |
| EXPECT_TRUE(test_app_.ProcessCommandLine("set encoding 5")); |
| EXPECT_TRUE(test_app_.ProcessCommandLine("set metric 4")); |
| |
| // Set skip_shuffler true. |
| EXPECT_TRUE(test_app_.ProcessCommandLine("set skip_shuffler true")); |
| EXPECT_TRUE(NoOutput()); |
| |
| EXPECT_TRUE(test_app_.ProcessCommandLine("encode 100 index=0")); |
| EXPECT_TRUE(test_app_.ProcessCommandLine("encode 200 index=1")); |
| EXPECT_TRUE(test_app_.ProcessCommandLine("send")); |
| EXPECT_TRUE(NoOutput()); |
| // The received envelope should contain 1 batch. |
| envelope = fake_analyzer_client_->envelope; |
| ASSERT_EQ(1, envelope.batch_size()); |
| // That batch should contain 300 messages. |
| const ObservationBatch& batch3 = envelope.batch(0); |
| EXPECT_EQ(300, batch3.encrypted_observation_size()); |
| // The metric ID should be 4. |
| EXPECT_EQ(4u, batch3.meta_data().metric_id()); |
| // All of the Observations should have a single part named "event" that has an |
| // encoding config ID of 5 |
| for (const auto& encrypted_message : batch3.encrypted_observation()) { |
| auto observation = ParseUnencryptedObservation(encrypted_message); |
| EXPECT_EQ(1, observation.parts_size()); |
| auto part = observation.parts().at("event"); |
| EXPECT_EQ(5u, part.encoding_config_id()); |
| } |
| } |
| |
| // Tests processing a multi-encode and send operation. |
| TEST_F(TestAppTest, ProcessCommandLineMultiEncodeAndSend) { |
| // The default is metric is 1. |
| EXPECT_TRUE(test_app_.ProcessCommandLine("encode 19 url:www.AAAA:1")); |
| EXPECT_TRUE(test_app_.ProcessCommandLine("encode 20 url:www.BBBB:1")); |
| EXPECT_TRUE(test_app_.ProcessCommandLine("send")); |
| EXPECT_TRUE(NoOutput()); |
| // The received envelope should contain 1 batch. |
| Envelope& envelope = fake_shuffler_client_->envelope; |
| ASSERT_EQ(1, envelope.batch_size()); |
| // That batch should contain 39 messages. |
| const ObservationBatch& batch = envelope.batch(0); |
| EXPECT_EQ(39, batch.encrypted_observation_size()); |
| // The metric ID should be the default value of 1. |
| EXPECT_EQ(1u, batch.meta_data().metric_id()); |
| // All of the Observations should have a single part named "url" that has an |
| // encoding config ID of the default value of 1. |
| for (const auto& encrypted_message : batch.encrypted_observation()) { |
| auto observation = ParseUnencryptedObservation(encrypted_message); |
| EXPECT_EQ(1, observation.parts_size()); |
| auto part = observation.parts().at("url"); |
| EXPECT_EQ(1u, part.encoding_config_id()); |
| } |
| |
| // Switch to metric 3 which is fruit rating. |
| EXPECT_TRUE(test_app_.ProcessCommandLine("set metric 3")); |
| |
| // Encode 100 instances of rating apple as 10 using encoding configs |
| // 3 and 4 respectively. |
| EXPECT_TRUE( |
| test_app_.ProcessCommandLine("encode 100 fruit:apple:3 rating:10:4")); |
| // Encode 200 instances of rating banana as 7 using encoding configs |
| // 3 and 4 respectively. |
| EXPECT_TRUE( |
| test_app_.ProcessCommandLine("encode 200 fruit:banana:3 rating:7:4")); |
| // Send |
| EXPECT_TRUE(test_app_.ProcessCommandLine("send")); |
| EXPECT_TRUE(NoOutput()); |
| envelope = fake_shuffler_client_->envelope; |
| ASSERT_EQ(1, envelope.batch_size()); |
| // That batch should contain 300 messages. |
| const ObservationBatch& batch2 = envelope.batch(0); |
| EXPECT_EQ(300, batch2.encrypted_observation_size()); |
| // The metric ID should be 3. |
| EXPECT_EQ(3u, batch2.meta_data().metric_id()); |
| // All of the Observations should have two parts named fruit and rating. |
| for (const auto& encrypted_message : batch2.encrypted_observation()) { |
| auto observation = ParseUnencryptedObservation(encrypted_message); |
| EXPECT_EQ(2, observation.parts_size()); |
| auto fruit_part = observation.parts().at("fruit"); |
| auto rating_part = observation.parts().at("rating"); |
| EXPECT_EQ(3u, fruit_part.encoding_config_id()); |
| EXPECT_EQ(4u, rating_part.encoding_config_id()); |
| } |
| } |
| |
| // Tests processing an encode and send operation with more than one |
| // batch in the envelope. |
| TEST_F(TestAppTest, ProcessCommandLineEncodeAndSendMulti) { |
| // The default is metric 1 encoding 1 which is Forculus with |
| // URLs. |
| EXPECT_TRUE(test_app_.ProcessCommandLine("encode 19 www.AAAA")); |
| EXPECT_TRUE(test_app_.ProcessCommandLine("encode 20 www.BBBB")); |
| EXPECT_TRUE(NoOutput()); |
| |
| // Notice we do not send! |
| |
| // Switch to metric 2 encoding 2 which is Basic RAPPOR with |
| // hours-of-the-day. |
| EXPECT_TRUE(test_app_.ProcessCommandLine("set encoding 2")); |
| EXPECT_TRUE(test_app_.ProcessCommandLine("set metric 2")); |
| |
| EXPECT_TRUE(test_app_.ProcessCommandLine("encode 100 8")); |
| EXPECT_TRUE(test_app_.ProcessCommandLine("encode 200 9")); |
| |
| // Now we send. |
| EXPECT_TRUE(test_app_.ProcessCommandLine("send")); |
| EXPECT_TRUE(NoOutput()); |
| |
| // The received envelope should contain 2 batches. |
| Envelope& envelope = fake_shuffler_client_->envelope; |
| ASSERT_EQ(2, envelope.batch_size()); |
| |
| // The first batch should contain 39 messages. |
| const ObservationBatch& batch = envelope.batch(0); |
| EXPECT_EQ(39, batch.encrypted_observation_size()); |
| // The metric ID should be the default value of 1. |
| EXPECT_EQ(1u, batch.meta_data().metric_id()); |
| // All of the Observations should have a single part named "url" that has an |
| // encoding config ID of the default value of 1. |
| for (const auto& encrypted_message : batch.encrypted_observation()) { |
| auto observation = ParseUnencryptedObservation(encrypted_message); |
| EXPECT_EQ(1, observation.parts_size()); |
| auto part = observation.parts().at("url"); |
| EXPECT_EQ(1u, part.encoding_config_id()); |
| } |
| |
| // The second batch should contain 300 messages. |
| const ObservationBatch& batch2 = envelope.batch(1); |
| EXPECT_EQ(300, batch2.encrypted_observation_size()); |
| // The metric ID should be 2. |
| EXPECT_EQ(2u, batch2.meta_data().metric_id()); |
| // All of the Observations should have a single part named "hour" that has an |
| // encoding config ID of 2 |
| for (const auto& encrypted_message : batch2.encrypted_observation()) { |
| auto observation = ParseUnencryptedObservation(encrypted_message); |
| EXPECT_EQ(1, observation.parts_size()); |
| auto part = observation.parts().at("hour"); |
| EXPECT_EQ(2u, part.encoding_config_id()); |
| } |
| } |
| |
| // Tests processing the "quit" command |
| TEST_F(TestAppTest, ProcessCommandLineQuit) { |
| EXPECT_FALSE(test_app_.ProcessCommandLine("quit")); |
| EXPECT_TRUE(NoOutput()); |
| } |
| |
| ////////////////////////////////////// |
| // Tests of send-once mode. |
| ///////////////////////////////////// |
| |
| // Tests the Run() method in send-once mode. |
| TEST_F(TestAppTest, RunSendAndQuit) { |
| test_app_.set_mode(TestApp::kSendOnce); |
| test_app_.set_metric(3); |
| FLAGS_num_clients = 31; |
| FLAGS_values = "fruit:apple:3,rating:10:4"; |
| test_app_.Run(); |
| EXPECT_TRUE(NoOutput()); |
| |
| // The envelope should contain a single batch. |
| Envelope& envelope = fake_shuffler_client_->envelope; |
| ASSERT_EQ(1, envelope.batch_size()); |
| |
| // That batch should contain 31 messages. |
| const ObservationBatch& batch = envelope.batch(0); |
| EXPECT_EQ(31, batch.encrypted_observation_size()); |
| // The metric ID should be 3. |
| EXPECT_EQ(3u, batch.meta_data().metric_id()); |
| // All of the Observations should have two parts named fruit and rating. |
| for (const auto& encrypted_message : batch.encrypted_observation()) { |
| auto observation = ParseUnencryptedObservation(encrypted_message); |
| EXPECT_EQ(2, observation.parts_size()); |
| auto fruit_part = observation.parts().at("fruit"); |
| auto rating_part = observation.parts().at("rating"); |
| EXPECT_EQ(3u, fruit_part.encoding_config_id()); |
| EXPECT_EQ(4u, rating_part.encoding_config_id()); |
| } |
| } |
| |
| // Tests the Run() method in send-once mode with invalid flags. |
| TEST_F(TestAppTest, RunSendAndQuitBad) { |
| test_app_.set_mode(TestApp::kSendOnce); |
| test_app_.set_metric(3); |
| |
| // Misspell "fruit" |
| FLAGS_values = "fruits:apple:3,rating:10:4"; |
| test_app_.Run(); |
| // The envelope should be empty. |
| Envelope& envelope = fake_shuffler_client_->envelope; |
| ASSERT_EQ(0, envelope.batch_size()); |
| |
| // Misspell "apple" |
| FLAGS_values = "fruit:apples:3,rating:10:4"; |
| test_app_.Run(); |
| // The envelope should be empty. |
| envelope = fake_shuffler_client_->envelope; |
| ASSERT_EQ(0, envelope.batch_size()); |
| |
| // Write "x" in place of "3" |
| FLAGS_values = "fruit:apple:x,rating:10:4"; |
| test_app_.Run(); |
| // The envelope should be empty. |
| envelope = fake_shuffler_client_->envelope; |
| ASSERT_EQ(0, envelope.batch_size()); |
| |
| // Write "-3" in place of "3" |
| FLAGS_values = "fruit:apple:-3,rating:10:4"; |
| test_app_.Run(); |
| // The envelope should be empty. |
| envelope = fake_shuffler_client_->envelope; |
| ASSERT_EQ(0, envelope.batch_size()); |
| |
| // Miss the comma. |
| FLAGS_values = "fruit:apple:3 rating:10:4"; |
| test_app_.Run(); |
| // The envelope should be empty. |
| envelope = fake_shuffler_client_->envelope; |
| ASSERT_EQ(0, envelope.batch_size()); |
| |
| // Miss the third part of the second triple |
| FLAGS_values = "fruit:apple:3,rating:10:"; |
| test_app_.Run(); |
| // The envelope should be empty. |
| envelope = fake_shuffler_client_->envelope; |
| ASSERT_EQ(0, envelope.batch_size()); |
| } |
| |
| } // namespace cobalt |
| |
| int main(int argc, char** argv) { |
| ::testing::InitGoogleTest(&argc, argv); |
| google::ParseCommandLineFlags(&argc, &argv, true); |
| google::InitGoogleLogging(argv[0]); |
| return RUN_ALL_TESTS(); |
| } |