blob: b3a47bae806433918d564bea62174485934f98d7 [file] [log] [blame]
// Copyright (C) 2022 Google LLC
//
// 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.
#include "icing/scoring/advanced_scoring/advanced-scorer.h"
#include <cmath>
#include <memory>
#include <string>
#include <string_view>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "icing/document-builder.h"
#include "icing/file/filesystem.h"
#include "icing/index/hit/doc-hit-info.h"
#include "icing/join/join-children-fetcher.h"
#include "icing/proto/document.pb.h"
#include "icing/proto/schema.pb.h"
#include "icing/proto/scoring.pb.h"
#include "icing/proto/usage.pb.h"
#include "icing/schema-builder.h"
#include "icing/schema/schema-store.h"
#include "icing/scoring/scorer-factory.h"
#include "icing/scoring/scorer.h"
#include "icing/store/document-id.h"
#include "icing/store/document-store.h"
#include "icing/testing/common-matchers.h"
#include "icing/testing/fake-clock.h"
#include "icing/testing/tmp-directory.h"
namespace icing {
namespace lib {
namespace {
using ::testing::DoubleNear;
using ::testing::Eq;
class AdvancedScorerTest : public testing::Test {
protected:
AdvancedScorerTest()
: test_dir_(GetTestTempDir() + "/icing"),
doc_store_dir_(test_dir_ + "/doc_store"),
schema_store_dir_(test_dir_ + "/schema_store") {}
void SetUp() override {
filesystem_.DeleteDirectoryRecursively(test_dir_.c_str());
filesystem_.CreateDirectoryRecursively(doc_store_dir_.c_str());
filesystem_.CreateDirectoryRecursively(schema_store_dir_.c_str());
ICING_ASSERT_OK_AND_ASSIGN(
schema_store_,
SchemaStore::Create(&filesystem_, schema_store_dir_, &fake_clock_));
ICING_ASSERT_OK_AND_ASSIGN(
DocumentStore::CreateResult create_result,
DocumentStore::Create(&filesystem_, doc_store_dir_, &fake_clock_,
schema_store_.get()));
document_store_ = std::move(create_result.document_store);
// Creates a simple email schema
SchemaProto test_email_schema =
SchemaBuilder()
.AddType(SchemaTypeConfigBuilder().SetType("email").AddProperty(
PropertyConfigBuilder()
.SetName("subject")
.SetDataTypeString(
TermMatchType::PREFIX,
StringIndexingConfig::TokenizerType::PLAIN)
.SetDataType(TYPE_STRING)
.SetCardinality(CARDINALITY_OPTIONAL)))
.Build();
ICING_ASSERT_OK(schema_store_->SetSchema(test_email_schema));
}
void TearDown() override {
document_store_.reset();
schema_store_.reset();
filesystem_.DeleteDirectoryRecursively(test_dir_.c_str());
}
const std::string test_dir_;
const std::string doc_store_dir_;
const std::string schema_store_dir_;
Filesystem filesystem_;
std::unique_ptr<SchemaStore> schema_store_;
std::unique_ptr<DocumentStore> document_store_;
FakeClock fake_clock_;
};
constexpr double kEps = 0.0000000001;
constexpr int kDefaultScore = 0;
constexpr int64_t kDefaultCreationTimestampMs = 1571100001111;
DocumentProto CreateDocument(
const std::string& name_space, const std::string& uri,
int score = kDefaultScore,
int64_t creation_timestamp_ms = kDefaultCreationTimestampMs) {
return DocumentBuilder()
.SetKey(name_space, uri)
.SetSchema("email")
.SetScore(score)
.SetCreationTimestampMs(creation_timestamp_ms)
.Build();
}
UsageReport CreateUsageReport(std::string name_space, std::string uri,
int64_t timestamp_ms,
UsageReport::UsageType usage_type) {
UsageReport usage_report;
usage_report.set_document_namespace(name_space);
usage_report.set_document_uri(uri);
usage_report.set_usage_timestamp_ms(timestamp_ms);
usage_report.set_usage_type(usage_type);
return usage_report;
}
ScoringSpecProto CreateAdvancedScoringSpec(
const std::string& advanced_scoring_expression) {
ScoringSpecProto scoring_spec;
scoring_spec.set_rank_by(
ScoringSpecProto::RankingStrategy::ADVANCED_SCORING_EXPRESSION);
scoring_spec.set_advanced_scoring_expression(advanced_scoring_expression);
return scoring_spec;
}
TEST_F(AdvancedScorerTest, InvalidAdvancedScoringSpec) {
// Empty scoring expression for advanced scoring
ScoringSpecProto scoring_spec;
scoring_spec.set_rank_by(
ScoringSpecProto::RankingStrategy::ADVANCED_SCORING_EXPRESSION);
EXPECT_THAT(
scorer_factory::Create(scoring_spec, /*default_score=*/10,
document_store_.get(), schema_store_.get()),
StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
// Non-empty scoring expression for normal scoring
scoring_spec = ScoringSpecProto::default_instance();
scoring_spec.set_rank_by(ScoringSpecProto::RankingStrategy::DOCUMENT_SCORE);
scoring_spec.set_advanced_scoring_expression("1");
EXPECT_THAT(
scorer_factory::Create(scoring_spec, /*default_score=*/10,
document_store_.get(), schema_store_.get()),
StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
}
TEST_F(AdvancedScorerTest, SimpleExpression) {
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id,
document_store_->Put(CreateDocument("namespace", "uri")));
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<Scorer> scorer,
AdvancedScorer::Create(CreateAdvancedScoringSpec("123"),
/*default_score=*/10, document_store_.get(),
schema_store_.get()));
DocHitInfo docHitInfo = DocHitInfo(document_id);
EXPECT_THAT(scorer->GetScore(docHitInfo), Eq(123));
}
TEST_F(AdvancedScorerTest, BasicPureArithmeticExpression) {
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id,
document_store_->Put(CreateDocument("namespace", "uri")));
DocHitInfo docHitInfo = DocHitInfo(document_id);
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<Scorer> scorer,
AdvancedScorer::Create(CreateAdvancedScoringSpec("1 + 2"),
/*default_score=*/10, document_store_.get(),
schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), Eq(3));
ICING_ASSERT_OK_AND_ASSIGN(
scorer,
AdvancedScorer::Create(CreateAdvancedScoringSpec("-1 + 2"),
/*default_score=*/10, document_store_.get(),
schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), Eq(1));
ICING_ASSERT_OK_AND_ASSIGN(
scorer,
AdvancedScorer::Create(CreateAdvancedScoringSpec("1 + -2"),
/*default_score=*/10, document_store_.get(),
schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), Eq(-1));
ICING_ASSERT_OK_AND_ASSIGN(
scorer,
AdvancedScorer::Create(CreateAdvancedScoringSpec("1 - 2"),
/*default_score=*/10, document_store_.get(),
schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), Eq(-1));
ICING_ASSERT_OK_AND_ASSIGN(
scorer,
AdvancedScorer::Create(CreateAdvancedScoringSpec("1 * 2"),
/*default_score=*/10, document_store_.get(),
schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), Eq(2));
ICING_ASSERT_OK_AND_ASSIGN(
scorer,
AdvancedScorer::Create(CreateAdvancedScoringSpec("1 / 2"),
/*default_score=*/10, document_store_.get(),
schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), Eq(0.5));
}
TEST_F(AdvancedScorerTest, BasicMathFunctionExpression) {
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id,
document_store_->Put(CreateDocument("namespace", "uri")));
DocHitInfo docHitInfo = DocHitInfo(document_id);
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<Scorer> scorer,
AdvancedScorer::Create(CreateAdvancedScoringSpec("log(10, 1000)"),
/*default_score=*/10, document_store_.get(),
schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), DoubleNear(3, kEps));
ICING_ASSERT_OK_AND_ASSIGN(
scorer,
AdvancedScorer::Create(
CreateAdvancedScoringSpec("log(2.718281828459045)"),
/*default_score=*/10, document_store_.get(), schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), DoubleNear(1, kEps));
ICING_ASSERT_OK_AND_ASSIGN(
scorer,
AdvancedScorer::Create(CreateAdvancedScoringSpec("pow(2, 10)"),
/*default_score=*/10, document_store_.get(),
schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), Eq(1024));
ICING_ASSERT_OK_AND_ASSIGN(
scorer,
AdvancedScorer::Create(
CreateAdvancedScoringSpec("max(10, 11, 12, 13, 14)"),
/*default_score=*/10, document_store_.get(), schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), Eq(14));
ICING_ASSERT_OK_AND_ASSIGN(
scorer,
AdvancedScorer::Create(
CreateAdvancedScoringSpec("min(10, 11, 12, 13, 14)"),
/*default_score=*/10, document_store_.get(), schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), Eq(10));
ICING_ASSERT_OK_AND_ASSIGN(
scorer,
AdvancedScorer::Create(
CreateAdvancedScoringSpec("len(10, 11, 12, 13, 14)"),
/*default_score=*/10, document_store_.get(), schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), Eq(5));
ICING_ASSERT_OK_AND_ASSIGN(
scorer,
AdvancedScorer::Create(
CreateAdvancedScoringSpec("sum(10, 11, 12, 13, 14)"),
/*default_score=*/10, document_store_.get(), schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), Eq(10 + 11 + 12 + 13 + 14));
ICING_ASSERT_OK_AND_ASSIGN(
scorer,
AdvancedScorer::Create(
CreateAdvancedScoringSpec("avg(10, 11, 12, 13, 14)"),
/*default_score=*/10, document_store_.get(), schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), Eq((10 + 11 + 12 + 13 + 14) / 5.));
ICING_ASSERT_OK_AND_ASSIGN(
scorer,
AdvancedScorer::Create(CreateAdvancedScoringSpec("sqrt(2)"),
/*default_score=*/10, document_store_.get(),
schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), DoubleNear(sqrt(2), kEps));
ICING_ASSERT_OK_AND_ASSIGN(
scorer,
AdvancedScorer::Create(CreateAdvancedScoringSpec("abs(-2) + abs(2)"),
/*default_score=*/10, document_store_.get(),
schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), Eq(4));
ICING_ASSERT_OK_AND_ASSIGN(
scorer,
AdvancedScorer::Create(
CreateAdvancedScoringSpec("sin(3.141592653589793)"),
/*default_score=*/10, document_store_.get(), schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), DoubleNear(0, kEps));
ICING_ASSERT_OK_AND_ASSIGN(
scorer,
AdvancedScorer::Create(
CreateAdvancedScoringSpec("cos(3.141592653589793)"),
/*default_score=*/10, document_store_.get(), schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), DoubleNear(-1, kEps));
ICING_ASSERT_OK_AND_ASSIGN(
scorer,
AdvancedScorer::Create(
CreateAdvancedScoringSpec("tan(3.141592653589793 / 4)"),
/*default_score=*/10, document_store_.get(), schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), DoubleNear(1, kEps));
}
TEST_F(AdvancedScorerTest, DocumentScoreCreationTimestampFunctionExpression) {
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id,
document_store_->Put(CreateDocument(
"namespace", "uri", /*score=*/123,
/*creation_timestamp_ms=*/kDefaultCreationTimestampMs)));
DocHitInfo docHitInfo = DocHitInfo(document_id);
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<Scorer> scorer,
AdvancedScorer::Create(CreateAdvancedScoringSpec("this.documentScore()"),
/*default_score=*/10, document_store_.get(),
schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), Eq(123));
ICING_ASSERT_OK_AND_ASSIGN(
scorer,
AdvancedScorer::Create(
CreateAdvancedScoringSpec("this.creationTimestamp()"),
/*default_score=*/10, document_store_.get(), schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), Eq(kDefaultCreationTimestampMs));
ICING_ASSERT_OK_AND_ASSIGN(
scorer,
AdvancedScorer::Create(
CreateAdvancedScoringSpec(
"this.documentScore() + this.creationTimestamp()"),
/*default_score=*/10, document_store_.get(), schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo),
Eq(123 + kDefaultCreationTimestampMs));
}
TEST_F(AdvancedScorerTest, DocumentUsageFunctionExpression) {
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id,
document_store_->Put(CreateDocument("namespace", "uri")));
DocHitInfo docHitInfo = DocHitInfo(document_id);
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<Scorer> scorer,
AdvancedScorer::Create(
CreateAdvancedScoringSpec("this.usageCount(1) + this.usageCount(2) "
"+ this.usageLastUsedTimestamp(3)"),
/*default_score=*/10, document_store_.get(), schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), Eq(0));
ICING_ASSERT_OK(document_store_->ReportUsage(
CreateUsageReport("namespace", "uri", 100000, UsageReport::USAGE_TYPE1)));
EXPECT_THAT(scorer->GetScore(docHitInfo), Eq(1));
ICING_ASSERT_OK(document_store_->ReportUsage(
CreateUsageReport("namespace", "uri", 200000, UsageReport::USAGE_TYPE2)));
EXPECT_THAT(scorer->GetScore(docHitInfo), Eq(2));
ICING_ASSERT_OK(document_store_->ReportUsage(
CreateUsageReport("namespace", "uri", 300000, UsageReport::USAGE_TYPE3)));
EXPECT_THAT(scorer->GetScore(docHitInfo), Eq(300002));
ICING_ASSERT_OK_AND_ASSIGN(
scorer,
AdvancedScorer::Create(
CreateAdvancedScoringSpec("this.usageLastUsedTimestamp(1)"),
/*default_score=*/10, document_store_.get(), schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), Eq(100000));
ICING_ASSERT_OK_AND_ASSIGN(
scorer,
AdvancedScorer::Create(
CreateAdvancedScoringSpec("this.usageLastUsedTimestamp(2)"),
/*default_score=*/10, document_store_.get(), schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), Eq(200000));
ICING_ASSERT_OK_AND_ASSIGN(
scorer,
AdvancedScorer::Create(
CreateAdvancedScoringSpec("this.usageLastUsedTimestamp(3)"),
/*default_score=*/10, document_store_.get(), schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), Eq(300000));
}
TEST_F(AdvancedScorerTest, DocumentUsageFunctionOutOfRange) {
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id,
document_store_->Put(CreateDocument("namespace", "uri")));
DocHitInfo docHitInfo = DocHitInfo(document_id);
const double default_score = 123;
// Should get default score for the following expressions that cause "runtime"
// errors.
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<Scorer> scorer,
AdvancedScorer::Create(CreateAdvancedScoringSpec("this.usageCount(4)"),
default_score, document_store_.get(),
schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), Eq(default_score));
ICING_ASSERT_OK_AND_ASSIGN(
scorer, AdvancedScorer::Create(
CreateAdvancedScoringSpec("this.usageCount(0)"),
default_score, document_store_.get(), schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), Eq(default_score));
ICING_ASSERT_OK_AND_ASSIGN(
scorer, AdvancedScorer::Create(
CreateAdvancedScoringSpec("this.usageCount(1.5)"),
default_score, document_store_.get(), schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), Eq(default_score));
}
// scoring-processor_test.cc will help to get better test coverage for relevance
// score.
TEST_F(AdvancedScorerTest, RelevanceScoreFunctionScoreExpression) {
DocumentProto test_document =
DocumentBuilder()
.SetScore(5)
.SetKey("namespace", "uri")
.SetSchema("email")
.AddStringProperty("subject", "subject foo")
.SetCreationTimestampMs(kDefaultCreationTimestampMs)
.Build();
ICING_ASSERT_OK_AND_ASSIGN(DocumentId document_id,
document_store_->Put(test_document));
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<AdvancedScorer> scorer,
AdvancedScorer::Create(CreateAdvancedScoringSpec("this.relevanceScore()"),
/*default_score=*/10, document_store_.get(),
schema_store_.get()));
scorer->PrepareToScore(/*query_term_iterators=*/{});
// Should get the default score.
DocHitInfo docHitInfo = DocHitInfo(document_id);
EXPECT_THAT(scorer->GetScore(docHitInfo, /*query_it=*/nullptr), Eq(10));
}
TEST_F(AdvancedScorerTest, ChildrenScoresFunctionScoreExpression) {
const double default_score = 123;
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id_1,
document_store_->Put(CreateDocument("namespace", "uri1")));
DocHitInfo docHitInfo1 = DocHitInfo(document_id_1);
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id_2,
document_store_->Put(CreateDocument("namespace", "uri2")));
DocHitInfo docHitInfo2 = DocHitInfo(document_id_2);
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id_3,
document_store_->Put(CreateDocument("namespace", "uri3")));
DocHitInfo docHitInfo3 = DocHitInfo(document_id_3);
// Create a JoinChildrenFetcher that matches:
// document_id_1 to fake_child1 with score 1 and fake_child2 with score 2.
// document_id_2 to fake_child3 with score 4.
// document_id_3 has no child.
JoinSpecProto join_spec;
join_spec.set_parent_property_expression("this.qualifiedId()");
join_spec.set_child_property_expression("sender");
std::unordered_map<DocumentId, std::vector<ScoredDocumentHit>>
map_joinable_qualified_id;
ScoredDocumentHit fake_child1(/*document_id=*/10, kSectionIdMaskNone,
/*score=*/1.0);
ScoredDocumentHit fake_child2(/*document_id=*/11, kSectionIdMaskNone,
/*score=*/2.0);
ScoredDocumentHit fake_child3(/*document_id=*/12, kSectionIdMaskNone,
/*score=*/4.0);
map_joinable_qualified_id[document_id_1].push_back(fake_child1);
map_joinable_qualified_id[document_id_1].push_back(fake_child2);
map_joinable_qualified_id[document_id_2].push_back(fake_child3);
JoinChildrenFetcher fetcher(join_spec, std::move(map_joinable_qualified_id));
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<AdvancedScorer> scorer,
AdvancedScorer::Create(
CreateAdvancedScoringSpec("len(this.childrenScores())"),
default_score, document_store_.get(), schema_store_.get(), &fetcher));
// document_id_1 has two children.
EXPECT_THAT(scorer->GetScore(docHitInfo1, /*query_it=*/nullptr), Eq(2));
// document_id_2 has one child.
EXPECT_THAT(scorer->GetScore(docHitInfo2, /*query_it=*/nullptr), Eq(1));
// document_id_3 has no child.
EXPECT_THAT(scorer->GetScore(docHitInfo3, /*query_it=*/nullptr), Eq(0));
ICING_ASSERT_OK_AND_ASSIGN(
scorer,
AdvancedScorer::Create(
CreateAdvancedScoringSpec("sum(this.childrenScores())"),
default_score, document_store_.get(), schema_store_.get(), &fetcher));
// document_id_1 has two children with scores 1 and 2.
EXPECT_THAT(scorer->GetScore(docHitInfo1, /*query_it=*/nullptr), Eq(3));
// document_id_2 has one child with score 4.
EXPECT_THAT(scorer->GetScore(docHitInfo2, /*query_it=*/nullptr), Eq(4));
// document_id_3 has no child.
EXPECT_THAT(scorer->GetScore(docHitInfo3, /*query_it=*/nullptr), Eq(0));
ICING_ASSERT_OK_AND_ASSIGN(
scorer,
AdvancedScorer::Create(
CreateAdvancedScoringSpec("avg(this.childrenScores())"),
default_score, document_store_.get(), schema_store_.get(), &fetcher));
// document_id_1 has two children with scores 1 and 2.
EXPECT_THAT(scorer->GetScore(docHitInfo1, /*query_it=*/nullptr), Eq(3 / 2.));
// document_id_2 has one child with score 4.
EXPECT_THAT(scorer->GetScore(docHitInfo2, /*query_it=*/nullptr), Eq(4 / 1.));
// document_id_3 has no child.
// This is an evaluation error, so default_score will be returned.
EXPECT_THAT(scorer->GetScore(docHitInfo3, /*query_it=*/nullptr),
Eq(default_score));
ICING_ASSERT_OK_AND_ASSIGN(
scorer,
AdvancedScorer::Create(
CreateAdvancedScoringSpec(
// Equivalent to "avg(this.childrenScores())"
"sum(this.childrenScores()) / len(this.childrenScores())"),
default_score, document_store_.get(), schema_store_.get(), &fetcher));
// document_id_1 has two children with scores 1 and 2.
EXPECT_THAT(scorer->GetScore(docHitInfo1, /*query_it=*/nullptr), Eq(3 / 2.));
// document_id_2 has one child with score 4.
EXPECT_THAT(scorer->GetScore(docHitInfo2, /*query_it=*/nullptr), Eq(4 / 1.));
// document_id_3 has no child.
// This is an evaluation error, so default_score will be returned.
EXPECT_THAT(scorer->GetScore(docHitInfo3, /*query_it=*/nullptr),
Eq(default_score));
}
TEST_F(AdvancedScorerTest, InvalidChildrenScoresFunctionScoreExpression) {
const double default_score = 123;
// Without join_children_fetcher provided, "len(this.childrenScores())" cannot
// be created.
EXPECT_THAT(AdvancedScorer::Create(
CreateAdvancedScoringSpec("len(this.childrenScores())"),
default_score, document_store_.get(), schema_store_.get(),
/*join_children_fetcher=*/nullptr),
StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
// The root expression can only be of double type, but here it is of list
// type.
JoinChildrenFetcher fake_fetcher(JoinSpecProto::default_instance(),
/*map_joinable_qualified_id=*/{});
EXPECT_THAT(
AdvancedScorer::Create(CreateAdvancedScoringSpec("this.childrenScores()"),
default_score, document_store_.get(),
schema_store_.get(), &fake_fetcher),
StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
}
TEST_F(AdvancedScorerTest, ComplexExpression) {
const int64_t creation_timestamp_ms = 123;
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id,
document_store_->Put(CreateDocument("namespace", "uri", /*score=*/123,
creation_timestamp_ms)));
DocHitInfo docHitInfo = DocHitInfo(document_id);
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<AdvancedScorer> scorer,
AdvancedScorer::Create(CreateAdvancedScoringSpec(
"pow(sin(2), 2)"
// This is this.usageCount(1)
"+ this.usageCount(this.documentScore() - 122)"
"/ 12.34"
"* (10 * pow(2 * 1, sin(2))"
"+ 10 * (2 + 10 + this.creationTimestamp()))"
// This should evaluate to default score.
"+ this.relevanceScore()"),
/*default_score=*/10, document_store_.get(),
schema_store_.get()));
EXPECT_FALSE(scorer->is_constant());
scorer->PrepareToScore(/*query_term_iterators=*/{});
ICING_ASSERT_OK(document_store_->ReportUsage(
CreateUsageReport("namespace", "uri", 0, UsageReport::USAGE_TYPE1)));
ICING_ASSERT_OK(document_store_->ReportUsage(
CreateUsageReport("namespace", "uri", 0, UsageReport::USAGE_TYPE1)));
EXPECT_THAT(scorer->GetScore(docHitInfo, /*query_it=*/nullptr),
DoubleNear(pow(sin(2), 2) +
2 / 12.34 *
(10 * pow(2 * 1, sin(2)) +
10 * (2 + 10 + creation_timestamp_ms)) +
10,
kEps));
}
TEST_F(AdvancedScorerTest, ConstantExpression) {
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<AdvancedScorer> scorer,
AdvancedScorer::Create(CreateAdvancedScoringSpec(
"pow(sin(2), 2)"
"+ log(2, 122) / 12.34"
"* (10 * pow(2 * 1, sin(2)) + 10 * (2 + 10))"),
/*default_score=*/10, document_store_.get(),
schema_store_.get()));
EXPECT_TRUE(scorer->is_constant());
}
// Should be a parsing Error
TEST_F(AdvancedScorerTest, EmptyExpression) {
EXPECT_THAT(
AdvancedScorer::Create(CreateAdvancedScoringSpec(""),
/*default_score=*/10, document_store_.get(),
schema_store_.get()),
StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
}
TEST_F(AdvancedScorerTest, EvaluationErrorShouldReturnDefaultScore) {
const double default_score = 123;
ICING_ASSERT_OK_AND_ASSIGN(
DocumentId document_id,
document_store_->Put(CreateDocument("namespace", "uri")));
DocHitInfo docHitInfo = DocHitInfo(document_id);
ICING_ASSERT_OK_AND_ASSIGN(
std::unique_ptr<Scorer> scorer,
AdvancedScorer::Create(CreateAdvancedScoringSpec("log(0)"), default_score,
document_store_.get(), schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), DoubleNear(default_score, kEps));
ICING_ASSERT_OK_AND_ASSIGN(
scorer,
AdvancedScorer::Create(CreateAdvancedScoringSpec("1 / 0"), default_score,
document_store_.get(), schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), DoubleNear(default_score, kEps));
ICING_ASSERT_OK_AND_ASSIGN(
scorer, AdvancedScorer::Create(CreateAdvancedScoringSpec("sqrt(-1)"),
default_score, document_store_.get(),
schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), DoubleNear(default_score, kEps));
ICING_ASSERT_OK_AND_ASSIGN(
scorer, AdvancedScorer::Create(CreateAdvancedScoringSpec("pow(-1, 0.5)"),
default_score, document_store_.get(),
schema_store_.get()));
EXPECT_THAT(scorer->GetScore(docHitInfo), DoubleNear(default_score, kEps));
}
// The following tests should trigger a type error while the visitor tries to
// build a ScoreExpression object.
TEST_F(AdvancedScorerTest, MathTypeError) {
const double default_score = 0;
EXPECT_THAT(
AdvancedScorer::Create(CreateAdvancedScoringSpec("test"), default_score,
document_store_.get(), schema_store_.get()),
StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
EXPECT_THAT(
AdvancedScorer::Create(CreateAdvancedScoringSpec("log()"), default_score,
document_store_.get(), schema_store_.get()),
StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
EXPECT_THAT(AdvancedScorer::Create(CreateAdvancedScoringSpec("log(1, 2, 3)"),
default_score, document_store_.get(),
schema_store_.get()),
StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
EXPECT_THAT(AdvancedScorer::Create(CreateAdvancedScoringSpec("log(1, this)"),
default_score, document_store_.get(),
schema_store_.get()),
StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
EXPECT_THAT(
AdvancedScorer::Create(CreateAdvancedScoringSpec("pow(1)"), default_score,
document_store_.get(), schema_store_.get()),
StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
EXPECT_THAT(AdvancedScorer::Create(CreateAdvancedScoringSpec("sqrt(1, 2)"),
default_score, document_store_.get(),
schema_store_.get()),
StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
EXPECT_THAT(AdvancedScorer::Create(CreateAdvancedScoringSpec("abs(1, 2)"),
default_score, document_store_.get(),
schema_store_.get()),
StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
EXPECT_THAT(AdvancedScorer::Create(CreateAdvancedScoringSpec("sin(1, 2)"),
default_score, document_store_.get(),
schema_store_.get()),
StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
EXPECT_THAT(AdvancedScorer::Create(CreateAdvancedScoringSpec("cos(1, 2)"),
default_score, document_store_.get(),
schema_store_.get()),
StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
EXPECT_THAT(AdvancedScorer::Create(CreateAdvancedScoringSpec("tan(1, 2)"),
default_score, document_store_.get(),
schema_store_.get()),
StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
EXPECT_THAT(
AdvancedScorer::Create(CreateAdvancedScoringSpec("this"), default_score,
document_store_.get(), schema_store_.get()),
StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
EXPECT_THAT(
AdvancedScorer::Create(CreateAdvancedScoringSpec("-this"), default_score,
document_store_.get(), schema_store_.get()),
StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
EXPECT_THAT(AdvancedScorer::Create(CreateAdvancedScoringSpec("1 + this"),
default_score, document_store_.get(),
schema_store_.get()),
StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
}
TEST_F(AdvancedScorerTest, DocumentFunctionTypeError) {
const double default_score = 0;
EXPECT_THAT(AdvancedScorer::Create(
CreateAdvancedScoringSpec("documentScore(1)"), default_score,
document_store_.get(), schema_store_.get()),
StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
EXPECT_THAT(AdvancedScorer::Create(
CreateAdvancedScoringSpec("this.creationTimestamp(1)"),
default_score, document_store_.get(), schema_store_.get()),
StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
EXPECT_THAT(AdvancedScorer::Create(
CreateAdvancedScoringSpec("this.usageCount()"), default_score,
document_store_.get(), schema_store_.get()),
StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
EXPECT_THAT(AdvancedScorer::Create(
CreateAdvancedScoringSpec("usageLastUsedTimestamp(1, 1)"),
default_score, document_store_.get(), schema_store_.get()),
StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
EXPECT_THAT(AdvancedScorer::Create(
CreateAdvancedScoringSpec("relevanceScore(1)"), default_score,
document_store_.get(), schema_store_.get()),
StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
EXPECT_THAT(AdvancedScorer::Create(
CreateAdvancedScoringSpec("documentScore(this)"),
default_score, document_store_.get(), schema_store_.get()),
StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
EXPECT_THAT(AdvancedScorer::Create(
CreateAdvancedScoringSpec("that.documentScore()"),
default_score, document_store_.get(), schema_store_.get()),
StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
EXPECT_THAT(AdvancedScorer::Create(
CreateAdvancedScoringSpec("this.this.creationTimestamp()"),
default_score, document_store_.get(), schema_store_.get()),
StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
EXPECT_THAT(AdvancedScorer::Create(CreateAdvancedScoringSpec("this.log(2)"),
default_score, document_store_.get(),
schema_store_.get()),
StatusIs(libtextclassifier3::StatusCode::INVALID_ARGUMENT));
}
} // namespace
} // namespace lib
} // namespace icing