blob: 41233eaaaf335e756e0fc4b2c9e9660ada5b6496 [file] [log] [blame]
// Copyright 2010-2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "rewriter/calculator_rewriter.h"
#include <string>
#include "base/logging.h"
#include "base/system_util.h"
#include "config/config.pb.h"
#include "config/config_handler.h"
#include "converter/conversion_request.h"
#include "converter/converter_interface.h"
#include "converter/converter_mock.h"
#include "converter/segments.h"
#include "engine/engine_interface.h"
#include "engine/mock_data_engine_factory.h"
#include "rewriter/calculator/calculator_interface.h"
#include "rewriter/calculator/calculator_mock.h"
#include "testing/base/public/gunit.h"
DECLARE_string(test_tmpdir);
namespace mozc {
namespace {
void AddCandidate(const string &key, const string &value, Segment *segment) {
Segment::Candidate *candidate = segment->add_candidate();
candidate->Init();
candidate->value = value;
candidate->content_value = value;
candidate->content_key = key;
}
void AddSegment(const string &key, const string &value, Segments *segments) {
Segment *segment = segments->push_back_segment();
segment->set_key(key);
AddCandidate(key, value, segment);
}
void SetSegment(const string &key, const string &value, Segments *segments) {
segments->Clear();
AddSegment(key, value, segments);
}
// "の計算結果"
const char kPartOfCalculationDescription[] =
"\xE3\x81\xAE\xE8\xA8\x88\xE7\xAE\x97\xE7\xB5\x90\xE6\x9E\x9C";
bool ContainsCalculatedResult(const Segment::Candidate &candidate) {
return candidate.description.find(kPartOfCalculationDescription) !=
string::npos;
}
// If the segment has a candidate which was inserted by CalculatorRewriter,
// then return its index. Otherwise return -1.
int GetIndexOfCalculatedCandidate(const Segments &segments) {
CHECK_EQ(segments.segments_size(), 1);
for (size_t i = 0; i < segments.segment(0).candidates_size(); ++i) {
const Segment::Candidate &candidate = segments.segment(0).candidate(i);
// Description includes "の計算結果"
if (ContainsCalculatedResult(candidate)) {
return i;
}
}
return -1;
}
} // anonymous namespace
class CalculatorRewriterTest : public testing::Test {
protected:
static bool InsertCandidate(const CalculatorRewriter &calculator_rewriter,
const string &value,
size_t insert_pos,
Segment *segment) {
return calculator_rewriter.InsertCandidate(value, insert_pos, segment);
}
CalculatorMock &calculator_mock() {
return calculator_mock_;
}
CalculatorRewriter *BuildCalculatorRewriterWithConverterMock() {
converter_mock_.reset(new ConverterMock);
return new CalculatorRewriter(converter_mock_.get());
}
virtual void SetUp() {
SystemUtil::SetUserProfileDirectory(FLAGS_test_tmpdir);
// use mock
CalculatorFactory::SetCalculator(&calculator_mock_);
config::ConfigHandler::GetDefaultConfig(&default_config_);
config::Config config(default_config_);
config.set_use_calculator(true);
config::ConfigHandler::SetConfig(config);
}
virtual void TearDown() {
// Clear the mock test calculator
CalculatorFactory::SetCalculator(NULL);
config::ConfigHandler::SetConfig(default_config_);
}
private:
CalculatorMock calculator_mock_;
scoped_ptr<ConverterInterface> converter_mock_;
config::Config default_config_;
};
TEST_F(CalculatorRewriterTest, InsertCandidateTest) {
scoped_ptr<CalculatorRewriter> calculator_rewriter(
BuildCalculatorRewriterWithConverterMock());
{
Segment segment;
segment.set_key("key");
// Insertion should be failed if segment has no candidate beforehand
EXPECT_FALSE(InsertCandidate(*calculator_rewriter, "value", 0, &segment));
}
// Test insertion at each position of candidates list
for (int i = 0; i <= 2; ++i) {
Segment segment;
segment.set_key("key");
AddCandidate("key", "test", &segment);
AddCandidate("key", "test2", &segment);
EXPECT_TRUE(InsertCandidate(*calculator_rewriter, "value", i, &segment));
const Segment::Candidate &candidate = segment.candidate(i);
EXPECT_EQ(candidate.value, "value");
EXPECT_EQ(candidate.content_value, "value");
EXPECT_EQ(candidate.content_key, "key");
EXPECT_NE(0, candidate.attributes & Segment::Candidate::NO_LEARNING);
// Description should be "key の計算結果"
EXPECT_EQ(candidate.description, "key " +
string(kPartOfCalculationDescription));
}
}
TEST_F(CalculatorRewriterTest, BasicTest) {
// Pretend "key" is calculated to "value".
calculator_mock().SetCalculatePair("key", "value", true);
scoped_ptr<CalculatorRewriter> calculator_rewriter(
BuildCalculatorRewriterWithConverterMock());
const int counter_at_first = calculator_mock().calculation_counter();
const ConversionRequest request;
Segments segments;
SetSegment("test", "test", &segments);
calculator_rewriter->Rewrite(request, &segments);
EXPECT_EQ(GetIndexOfCalculatedCandidate(segments), -1);
EXPECT_EQ(calculator_mock().calculation_counter(), counter_at_first + 1);
SetSegment("key", "key", &segments);
calculator_rewriter->Rewrite(request, &segments);
int index = GetIndexOfCalculatedCandidate(segments);
EXPECT_NE(index, -1);
EXPECT_EQ(segments.segment(0).candidate(index).value, "value");
EXPECT_EQ(calculator_mock().calculation_counter(), counter_at_first + 2);
}
// CalculatorRewriter should convert an expression which is separated to
// multiple conversion segments. This test verifies it.
TEST_F(CalculatorRewriterTest, SeparatedSegmentsTest) {
// Pretend "1+1=" is calculated to "2".
calculator_mock().SetCalculatePair("1+1=", "2", true);
// Since this test depends on the actual implementation of
// Converter::ResizeSegments(), we cannot use converter mock here. However,
// the test itself is independent of data.
scoped_ptr<EngineInterface> engine_(MockDataEngineFactory::Create());
scoped_ptr<CalculatorRewriter> calculator_rewriter(
new CalculatorRewriter(engine_->GetConverter()));
const ConversionRequest request;
// Push back separated segments.
Segments segments;
AddSegment("1", "1", &segments);
AddSegment("+", "+", &segments);
AddSegment("1", "1", &segments);
AddSegment("=", "=", &segments);
calculator_rewriter->Rewrite(request, &segments);
EXPECT_EQ(segments.segments_size(), 1); // merged
int index = GetIndexOfCalculatedCandidate(segments);
EXPECT_NE(index, -1);
// Secondary result with expression (description: "1+1=2");
EXPECT_TRUE(ContainsCalculatedResult(
segments.segment(0).candidate(index + 1)));
EXPECT_EQ("2", segments.segment(0).candidate(index).value);
EXPECT_EQ("1+1=2", segments.segment(0).candidate(index + 1).value);
}
// Verify the description of calculator candidate.
TEST_F(CalculatorRewriterTest, DescriptionCheckTest) {
// "5・(8/4)ー7%3+6^−1*9="
const char kExpression[] =
"\xEF\xBC\x95\xE3\x83\xBB\xEF\xBC\x88\xEF\xBC\x98\xEF\xBC\x8F"
"\xEF\xBC\x94\xEF\xBC\x89\xE3\x83\xBC\xEF\xBC\x97\xEF\xBC\x85"
"\xEF\xBC\x93\xEF\xBC\x8B\xEF\xBC\x96\xEF\xBC\xBE\xE2\x88\x92"
"\xEF\xBC\x91\xEF\xBC\x8A\xEF\xBC\x99\xEF\xBC\x9D";
// Expected description
const string description =
"5/(8/4)-7%3+6^-1*9= " + string(kPartOfCalculationDescription);
// Pretend kExpression is calculated to "3"
calculator_mock().SetCalculatePair(kExpression, "3", true);
const ConversionRequest request;
scoped_ptr<CalculatorRewriter> calculator_rewriter(
BuildCalculatorRewriterWithConverterMock());
Segments segments;
AddSegment(kExpression, kExpression, &segments);
calculator_rewriter->Rewrite(request, &segments);
const int index = GetIndexOfCalculatedCandidate(segments);
EXPECT_EQ(segments.segment(0).candidate(index).description, description);
EXPECT_TRUE(ContainsCalculatedResult(
segments.segment(0).candidate(index + 1)));
}
TEST_F(CalculatorRewriterTest, ConfigTest) {
config::Config config;
config::ConfigHandler::GetDefaultConfig(&config);
calculator_mock().SetCalculatePair("1+1=", "2", true);
const ConversionRequest request;
// Since this test depends on the actual implementation of
// Converter::ResizeSegments(), we cannot use converter mock here. However,
// the test itself is independent of data.
scoped_ptr<EngineInterface> engine_(MockDataEngineFactory::Create());
scoped_ptr<CalculatorRewriter> calculator_rewriter(
new CalculatorRewriter(engine_->GetConverter()));
{
Segments segments;
AddSegment("1", "1", &segments);
AddSegment("+", "+", &segments);
AddSegment("1", "1", &segments);
AddSegment("=", "=", &segments);
config.set_use_calculator(true);
config::ConfigHandler::SetConfig(config);
EXPECT_TRUE(calculator_rewriter->Rewrite(request, &segments));
}
{
Segments segments;
AddSegment("1", "1", &segments);
AddSegment("+", "+", &segments);
AddSegment("1", "1", &segments);
AddSegment("=", "=", &segments);
config.set_use_calculator(false);
config::ConfigHandler::SetConfig(config);
EXPECT_FALSE(calculator_rewriter->Rewrite(request, &segments));
}
}
} // namespace mozc