| // Unit test for sandboxed_hunspell |
| |
| #include <optional> |
| #include <string> |
| |
| #include "devtools/build/runtime/get_runfiles_dir.h" |
| #include "file/base/filelineiter.h" |
| #include "file/base/path.h" |
| #include "testing/base/public/benchmark.h" |
| #include "testing/base/public/gmock.h" |
| #include "testing/base/public/gunit.h" |
| #include "third_party/absl/log/check.h" |
| #include "third_party/absl/log/log.h" |
| #include "third_party/absl/status/status.h" |
| #include "third_party/absl/strings/string_view.h" |
| #include "third_party/hunspell/sandbox/sandbox.h" |
| #include "third_party/hunspell/sandbox/sandboxed_hunspell.sapi.h" |
| #include "third_party/hunspell/src/hunspell/hunspell.h" |
| #include "third_party/sandboxed_api/util/status_matchers.h" |
| #include "third_party/sandboxed_api/vars.h" |
| |
| namespace { |
| |
| static constexpr absl::string_view test_dir_ = |
| "/tests"; |
| |
| static constexpr absl::string_view kAffixFileName = "utf8.aff"; |
| static constexpr absl::string_view kDictionaryFileName = "utf8.dic"; |
| |
| static constexpr absl::string_view kGoodFileName = "utf8.good"; |
| static constexpr absl::string_view kWrongFileName = "utf8_nonbmp.wrong"; |
| |
| static constexpr absl::string_view kSuggestion = "fo"; |
| static constexpr absl::string_view kRandomWord = "random_word123"; |
| |
| std::string GetTestFilePath(const absl::string_view& filename) { |
| return file::JoinPath(devtools_build::GetDataDependencyFilepath(test_dir_), |
| filename); |
| } |
| |
| class HunspellTest : public ::testing::Test { |
| public: |
| HunspellTest() |
| : s_affix_filename_(GetTestFilePath(kAffixFileName)), |
| s_dictionary_filename_(GetTestFilePath(kDictionaryFileName)), |
| sandbox_(s_affix_filename_, s_dictionary_filename_), |
| api_(&sandbox_) { |
| sapi::v::ConstCStr c_affix_filename(s_affix_filename_.c_str()); |
| sapi::v::ConstCStr c_dictionary_filename(s_dictionary_filename_.c_str()); |
| |
| CHECK(sandbox_.Init().ok()); |
| absl::StatusOr<sandboxed_hunspell::Hunhandle*> hunspell = |
| api_.Hunspell_create(c_affix_filename.PtrBefore(), |
| c_dictionary_filename.PtrBefore()); |
| CHECK(hunspell.ok()); |
| |
| hunspellrp_.emplace(*hunspell); |
| } |
| |
| ~HunspellTest() override { |
| if (hunspellrp_) { |
| absl::Status status = api_.Hunspell_destroy(&(*hunspellrp_)); |
| CHECK(status.ok()); |
| } |
| } |
| |
| protected: |
| const std::string s_affix_filename_; |
| const std::string s_dictionary_filename_; |
| sandboxed_hunspell::LibHunspellSapiSandbox sandbox_; |
| sandboxed_hunspell::LibHunspellApi api_; |
| std::optional<sapi::v::RemotePtr> hunspellrp_; |
| }; |
| |
| TEST_F(HunspellTest, CheckEncoding) { |
| SAPI_ASSERT_OK_AND_ASSIGN(char* ret, |
| api_.Hunspell_get_dic_encoding(&(*hunspellrp_))); |
| SAPI_ASSERT_OK_AND_ASSIGN(std::string encoding, api_.GetSandbox()->GetCString( |
| sapi::v::RemotePtr(ret))); |
| EXPECT_EQ(encoding, "UTF-8"); |
| } |
| |
| TEST_F(HunspellTest, CheckGoodSpell) { |
| SAPI_ASSERT_OK_AND_ASSIGN(char* _, |
| api_.Hunspell_get_dic_encoding(&(*hunspellrp_))); |
| |
| for (std::string& buf : |
| FileLines(GetTestFilePath(kGoodFileName), FileLineIterator::NO_LF)) { |
| sapi::v::ConstCStr cbuf(buf.c_str()); |
| SAPI_ASSERT_OK_AND_ASSIGN( |
| int result, api_.Hunspell_spell(&(*hunspellrp_), cbuf.PtrBefore())); |
| ASSERT_EQ(result, 1); |
| } |
| } |
| |
| TEST_F(HunspellTest, CheckWrongSpell) { |
| SAPI_ASSERT_OK_AND_ASSIGN(char* _, |
| api_.Hunspell_get_dic_encoding(&(*hunspellrp_))); |
| |
| for (std::string& buf : |
| FileLines(GetTestFilePath(kWrongFileName), FileLineIterator::NO_LF)) { |
| sapi::v::ConstCStr cbuf(buf.c_str()); |
| SAPI_ASSERT_OK_AND_ASSIGN( |
| int result, api_.Hunspell_spell(&(*hunspellrp_), cbuf.PtrBefore())); |
| ASSERT_EQ(result, 0); |
| } |
| } |
| |
| TEST_F(HunspellTest, CheckAddToDict) { |
| sapi::v::ConstCStr cbuf(kRandomWord.data()); |
| |
| int result; |
| SAPI_ASSERT_OK_AND_ASSIGN( |
| result, api_.Hunspell_spell(&(*hunspellrp_), cbuf.PtrBefore())); |
| ASSERT_EQ(result, 0); |
| |
| SAPI_ASSERT_OK_AND_ASSIGN( |
| result, api_.Hunspell_add(&(*hunspellrp_), cbuf.PtrBefore())); |
| ASSERT_EQ(result, 0); |
| |
| SAPI_ASSERT_OK_AND_ASSIGN( |
| result, api_.Hunspell_spell(&(*hunspellrp_), cbuf.PtrBefore())); |
| ASSERT_EQ(result, 1); |
| |
| SAPI_ASSERT_OK_AND_ASSIGN( |
| result, api_.Hunspell_remove(&(*hunspellrp_), cbuf.PtrBefore())); |
| ASSERT_EQ(result, 0); |
| |
| SAPI_ASSERT_OK_AND_ASSIGN( |
| result, api_.Hunspell_spell(&(*hunspellrp_), cbuf.PtrBefore())); |
| ASSERT_EQ(result, 0); |
| } |
| |
| TEST_F(HunspellTest, CheckSuggestion) { |
| sapi::v::ConstCStr cbuf(kSuggestion.data()); |
| |
| SAPI_ASSERT_OK_AND_ASSIGN( |
| int result, api_.Hunspell_spell(&(*hunspellrp_), cbuf.PtrBefore())); |
| ASSERT_EQ(result, 0); |
| |
| sapi::v::GenericPtr outptr; |
| SAPI_ASSERT_OK_AND_ASSIGN( |
| int nlist, api_.Hunspell_suggest(&(*hunspellrp_), outptr.PtrAfter(), |
| cbuf.PtrBefore())); |
| ASSERT_GT(nlist, 0); |
| } |
| |
| // The following go/benchmark tests allow viewing the performance overhead |
| // the sandboxed API causes. Due to the nature of SAPI sandboxes, the only |
| // reliable metric is `time/op`. |
| // Please note that every SAPI sandbox cold start takes ~10ms. |
| // |
| // benchy --perflab --runs 20 -benchtime 0.5s \ |
| // //third_party/hunspell/sandbox:sandboxed_hunspell_test |
| // |
| // name time/op |
| // BM_Sandboxed_hunspell_spell 228µs ± 2% |
| // BM_Unsandboxed_hunspell_spell 133µs ± 5% |
| |
| void BM_Sandboxed_hunspell_spell(benchmark::State& state) { |
| std::string suggestion = "fo"; |
| std::string s_affix_filename = GetTestFilePath("utf8.aff"); |
| std::string s_dictionary_filename = GetTestFilePath("utf8.dic"); |
| sandboxed_hunspell::LibHunspellSapiSandbox sandbox(s_affix_filename, |
| s_dictionary_filename); |
| |
| ASSERT_OK(sandbox.Init()); |
| sandboxed_hunspell::LibHunspellApi api(&sandbox); |
| |
| sapi::v::ConstCStr c_affix_filename(s_affix_filename.c_str()); |
| sapi::v::ConstCStr c_dictionary_filename(s_dictionary_filename.c_str()); |
| sapi::v::ConstCStr cbuf(suggestion.c_str()); |
| |
| for (const auto _ : state) { |
| SAPI_ASSERT_OK_AND_ASSIGN( |
| sandboxed_hunspell::Hunhandle * hunspell, |
| api.Hunspell_create(c_affix_filename.PtrBefore(), |
| c_dictionary_filename.PtrBefore())); |
| |
| sapi::v::RemotePtr hunspellrp(hunspell); |
| |
| SAPI_ASSERT_OK_AND_ASSIGN( |
| int result, api.Hunspell_spell(&hunspellrp, cbuf.PtrBefore())); |
| ASSERT_EQ(result, 0); |
| |
| absl::Status status = api.Hunspell_destroy(&hunspellrp); |
| ASSERT_THAT(status, sapi::IsOk()); |
| } |
| } |
| |
| void BM_Unsandboxed_hunspell_spell(benchmark::State& state) { |
| std::string suggestion = "fo"; |
| for (const auto _ : state) { |
| Hunhandle* hunspell = Hunspell_create(GetTestFilePath("utf8.aff").c_str(), |
| GetTestFilePath("utf8.dic").c_str()); |
| ASSERT_EQ(Hunspell_spell(hunspell, suggestion.c_str()), 0); |
| Hunspell_destroy(hunspell); |
| } |
| } |
| |
| BENCHMARK(BM_Sandboxed_hunspell_spell); |
| BENCHMARK(BM_Unsandboxed_hunspell_spell); |
| |
| } // namespace |