Commit aeccc43f for tesseract
commit aeccc43fb7577ecd99e7364a83c377f5c6c47905
Author: Stefan Weil <sw@weilnetz.de>
Date: Fri Nov 13 23:50:51 2026 +0100
unittest: use separate temporary directories for the LSTM trainer tests (#4702)
lstm_test, lstm_recode_test, lstm_squashed_test, lstmtrainer_test and
lang_model_test create language models with CombineLangModel() in
FLAGS_test_tmpdir and read them again. The first four of them share
the fixture LSTMTrainerTest, so they all wrote and read the same
files, for example ./tmp/eng/eng.traineddata, and lang_model_test
used that name, too.
"make -j check" runs the test programs at the same time, so one of
them can read such a file while another one is writing it. This is
the likely reason why lstm_test once got an empty unicharset in a
parallel test run. No line of the training data could be encoded
then, so the training iteration did not advance any more, and the
test wrote error messages to its log file until it was killed (15 GB
after 77 minutes).
Add file::MakeTmpdirForTestProgram(), which creates a subdirectory of
FLAGS_test_tmpdir with the name of the source file of the current
test, and use it for the language models and the checkpoints of these
five test programs.
Assisted-by: OpenCode / Claude Fable 5.2 (Anthropic)
Signed-off-by: Stefan Weil <sw@weilnetz.de>
diff --git a/unittest/include_gunit.h b/unittest/include_gunit.h
index ceac82f1..faa939b8 100644
--- a/unittest/include_gunit.h
+++ b/unittest/include_gunit.h
@@ -20,6 +20,7 @@
#include <filesystem>
#include <string>
+#include <system_error>
static const char *FLAGS_test_tmpdir = "./tmp";
@@ -46,6 +47,24 @@ public:
#endif
}
+ // Creates a directory for temporary files which is only used by the test
+ // program of the current test, and returns its name.
+ // Test programs can run at the same time ("make -j check"), so those which
+ // write files with the same names must not write them directly to
+ // FLAGS_test_tmpdir. The directory is named after the source file of the
+ // current test.
+ static std::string MakeTmpdirForTestProgram() {
+ MakeTmpdir();
+ std::string dir = FLAGS_test_tmpdir;
+ const auto *test_info = ::testing::UnitTest::GetInstance()->current_test_info();
+ if (test_info != nullptr && test_info->file() != nullptr) {
+ dir = JoinPath(dir, std::filesystem::path(test_info->file()).stem().string());
+ std::error_code ec;
+ std::filesystem::create_directory(dir, ec);
+ }
+ return dir;
+ }
+
// Create a file and write a string to it.
static bool WriteStringToFile(const std::string &contents, const std::string &filename) {
File::WriteStringToFileOrDie(contents, filename);
diff --git a/unittest/lang_model_test.cc b/unittest/lang_model_test.cc
index 048ee13e..e8e83f00 100644
--- a/unittest/lang_model_test.cc
+++ b/unittest/lang_model_test.cc
@@ -39,8 +39,7 @@ TEST(LangModelTest, AddACharacter) {
UNICHARSET unicharset;
EXPECT_TRUE(unicharset.load_from_file(unicharset_path.c_str()));
std::string version_str = "TestVersion";
- file::MakeTmpdir();
- std::string output_dir = FLAGS_test_tmpdir;
+ std::string output_dir = file::MakeTmpdirForTestProgram();
LOG(INFO) << "Output dir=" << output_dir << "\n";
std::string lang1 = "eng";
bool pass_through_recoder = false;
@@ -124,8 +123,7 @@ TEST(LangModelTest, AddACharacterHindi) {
UNICHARSET unicharset;
EXPECT_TRUE(unicharset.load_from_file(unicharset_path.c_str()));
std::string version_str = "TestVersion";
- file::MakeTmpdir();
- std::string output_dir = FLAGS_test_tmpdir;
+ std::string output_dir = file::MakeTmpdirForTestProgram();
LOG(INFO) << "Output dir=" << output_dir << "\n";
std::string lang1 = "hin";
bool pass_through_recoder = false;
diff --git a/unittest/lstm_test.h b/unittest/lstm_test.h
index 380c3476..fa18936f 100644
--- a/unittest/lstm_test.h
+++ b/unittest/lstm_test.h
@@ -73,13 +73,15 @@ protected:
ASSERT_TRUE(unicharset.load_from_file(unicharset_name.c_str(), false));
std::string script_dir = file::JoinPath(LANGDATA_DIR, "");
std::vector<std::string> words;
- EXPECT_EQ(0, CombineLangModel(unicharset, script_dir, "", FLAGS_test_tmpdir, kLang, !recode,
- words, words, words, false, nullptr, nullptr));
- std::string model_path = file::JoinPath(FLAGS_test_tmpdir, model_name);
+ // Several test programs use this fixture and write files with the same
+ // names, so each of them needs its own directory.
+ std::string tmpdir = file::MakeTmpdirForTestProgram();
+ EXPECT_EQ(0, CombineLangModel(unicharset, script_dir, "", tmpdir, kLang, !recode, words, words,
+ words, false, nullptr, nullptr));
+ std::string model_path = file::JoinPath(tmpdir, model_name);
std::string checkpoint_path = model_path + "_checkpoint";
trainer_ = std::make_unique<LSTMTrainer>(model_path.c_str(), checkpoint_path.c_str(), 0, 0);
- trainer_->InitCharSet(
- file::JoinPath(FLAGS_test_tmpdir, kLang, kLang) + ".traineddata");
+ trainer_->InitCharSet(file::JoinPath(tmpdir, kLang, kLang) + ".traineddata");
int net_mode = adam ? NF_ADAM : 0;
// Adam needs a higher learning rate, due to not multiplying the effective
// rate by 1/(1-momentum).