diff --git a/make.py b/make.py index 7431f67..fe08944 100644 --- a/make.py +++ b/make.py @@ -971,15 +971,21 @@ def _prepend_path(directory: Path) -> None: parts = current.split(sep) if current else [] # Case-insensitive comparison on Windows; exact match elsewhere. norm = (lambda s: s.lower()) if os.name == "nt" else (lambda s: s) - if any(norm(x) == norm(p) for x in parts if x): - return - os.environ["PATH"] = p + (sep + current if current else "") + already_on_path = any(norm(x) == norm(p) for x in parts if x) # GitHub Actions: persist the new entry across subsequent steps so that # tools installed by `make.py setup` are visible to later `run:` commands. + # This must run even when `directory` is already on the in-process PATH: + # being present on *this* process's PATH (e.g. because an earlier setup + # call already prepended it) says nothing about whether subsequent GitHub + # Actions steps — which are fresh processes — will see it. So append to + # GITHUB_PATH before the early-return that skips the in-process update. gha_path = os.environ.get("GITHUB_PATH") if gha_path: with open(gha_path, "a", encoding="utf-8") as fh: fh.write(p + "\n") + if already_on_path: + return + os.environ["PATH"] = p + (sep + current if current else "") def cmd_setup(args, ctx: "Context") -> int: diff --git a/test/cpp-tableau-loader/src/protoconf/hero_conf.pc.cc b/test/cpp-tableau-loader/src/protoconf/hero_conf.pc.cc index 847986f..f088c28 100644 --- a/test/cpp-tableau-loader/src/protoconf/hero_conf.pc.cc +++ b/test/cpp-tableau-loader/src/protoconf/hero_conf.pc.cc @@ -1,6 +1,6 @@ // Code generated by protoc-gen-cpp-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-cpp-tableau-loader v0.11.0 +// - protoc-gen-cpp-tableau-loader v0.12.0 // - protoc (unknown) // clang-format off // source: hero_conf.proto diff --git a/test/cpp-tableau-loader/src/protoconf/hero_conf.pc.h b/test/cpp-tableau-loader/src/protoconf/hero_conf.pc.h index 09922d6..a22aa3f 100644 --- a/test/cpp-tableau-loader/src/protoconf/hero_conf.pc.h +++ b/test/cpp-tableau-loader/src/protoconf/hero_conf.pc.h @@ -1,6 +1,6 @@ // Code generated by protoc-gen-cpp-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-cpp-tableau-loader v0.11.0 +// - protoc-gen-cpp-tableau-loader v0.12.0 // - protoc (unknown) // clang-format off // source: hero_conf.proto diff --git a/test/cpp-tableau-loader/src/protoconf/hub.pc.cc b/test/cpp-tableau-loader/src/protoconf/hub.pc.cc index 69c19d8..704f9a9 100644 --- a/test/cpp-tableau-loader/src/protoconf/hub.pc.cc +++ b/test/cpp-tableau-loader/src/protoconf/hub.pc.cc @@ -1,6 +1,6 @@ // Code generated by protoc-gen-cpp-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-cpp-tableau-loader v0.11.0 +// - protoc-gen-cpp-tableau-loader v0.12.0 // - protoc (unknown) // clang-format off diff --git a/test/cpp-tableau-loader/src/protoconf/hub.pc.h b/test/cpp-tableau-loader/src/protoconf/hub.pc.h index 612c45b..254c387 100644 --- a/test/cpp-tableau-loader/src/protoconf/hub.pc.h +++ b/test/cpp-tableau-loader/src/protoconf/hub.pc.h @@ -1,6 +1,6 @@ // Code generated by protoc-gen-cpp-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-cpp-tableau-loader v0.11.0 +// - protoc-gen-cpp-tableau-loader v0.12.0 // - protoc (unknown) // clang-format off diff --git a/test/cpp-tableau-loader/src/protoconf/hub_shard0.pc.cc b/test/cpp-tableau-loader/src/protoconf/hub_shard0.pc.cc index 198b922..6eb1a8f 100644 --- a/test/cpp-tableau-loader/src/protoconf/hub_shard0.pc.cc +++ b/test/cpp-tableau-loader/src/protoconf/hub_shard0.pc.cc @@ -1,6 +1,6 @@ // Code generated by protoc-gen-cpp-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-cpp-tableau-loader v0.11.0 +// - protoc-gen-cpp-tableau-loader v0.12.0 // - protoc (unknown) // clang-format off diff --git a/test/cpp-tableau-loader/src/protoconf/hub_shard1.pc.cc b/test/cpp-tableau-loader/src/protoconf/hub_shard1.pc.cc index 4a228bc..2a8c2f1 100644 --- a/test/cpp-tableau-loader/src/protoconf/hub_shard1.pc.cc +++ b/test/cpp-tableau-loader/src/protoconf/hub_shard1.pc.cc @@ -1,6 +1,6 @@ // Code generated by protoc-gen-cpp-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-cpp-tableau-loader v0.11.0 +// - protoc-gen-cpp-tableau-loader v0.12.0 // - protoc (unknown) // clang-format off diff --git a/test/cpp-tableau-loader/src/protoconf/index_conf.pc.cc b/test/cpp-tableau-loader/src/protoconf/index_conf.pc.cc index 532ca56..e54a07c 100644 --- a/test/cpp-tableau-loader/src/protoconf/index_conf.pc.cc +++ b/test/cpp-tableau-loader/src/protoconf/index_conf.pc.cc @@ -1,6 +1,6 @@ // Code generated by protoc-gen-cpp-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-cpp-tableau-loader v0.11.0 +// - protoc-gen-cpp-tableau-loader v0.12.0 // - protoc (unknown) // clang-format off // source: index_conf.proto diff --git a/test/cpp-tableau-loader/src/protoconf/index_conf.pc.h b/test/cpp-tableau-loader/src/protoconf/index_conf.pc.h index 7205607..15ea82c 100644 --- a/test/cpp-tableau-loader/src/protoconf/index_conf.pc.h +++ b/test/cpp-tableau-loader/src/protoconf/index_conf.pc.h @@ -1,6 +1,6 @@ // Code generated by protoc-gen-cpp-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-cpp-tableau-loader v0.11.0 +// - protoc-gen-cpp-tableau-loader v0.12.0 // - protoc (unknown) // clang-format off // source: index_conf.proto diff --git a/test/cpp-tableau-loader/src/protoconf/item_conf.pc.cc b/test/cpp-tableau-loader/src/protoconf/item_conf.pc.cc index 1edc25e..8c4e44c 100644 --- a/test/cpp-tableau-loader/src/protoconf/item_conf.pc.cc +++ b/test/cpp-tableau-loader/src/protoconf/item_conf.pc.cc @@ -1,6 +1,6 @@ // Code generated by protoc-gen-cpp-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-cpp-tableau-loader v0.11.0 +// - protoc-gen-cpp-tableau-loader v0.12.0 // - protoc (unknown) // clang-format off // source: item_conf.proto diff --git a/test/cpp-tableau-loader/src/protoconf/item_conf.pc.h b/test/cpp-tableau-loader/src/protoconf/item_conf.pc.h index 42f9cb1..a8f16e8 100644 --- a/test/cpp-tableau-loader/src/protoconf/item_conf.pc.h +++ b/test/cpp-tableau-loader/src/protoconf/item_conf.pc.h @@ -1,6 +1,6 @@ // Code generated by protoc-gen-cpp-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-cpp-tableau-loader v0.11.0 +// - protoc-gen-cpp-tableau-loader v0.12.0 // - protoc (unknown) // clang-format off // source: item_conf.proto diff --git a/test/cpp-tableau-loader/src/protoconf/load.pc.cc b/test/cpp-tableau-loader/src/protoconf/load.pc.cc index e39634a..562f8d7 100644 --- a/test/cpp-tableau-loader/src/protoconf/load.pc.cc +++ b/test/cpp-tableau-loader/src/protoconf/load.pc.cc @@ -1,6 +1,6 @@ // Code generated by protoc-gen-cpp-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-cpp-tableau-loader v0.11.0 +// - protoc-gen-cpp-tableau-loader v0.12.0 // - protoc (unknown) // clang-format off diff --git a/test/cpp-tableau-loader/src/protoconf/load.pc.h b/test/cpp-tableau-loader/src/protoconf/load.pc.h index df58444..17f548a 100644 --- a/test/cpp-tableau-loader/src/protoconf/load.pc.h +++ b/test/cpp-tableau-loader/src/protoconf/load.pc.h @@ -1,6 +1,6 @@ // Code generated by protoc-gen-cpp-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-cpp-tableau-loader v0.11.0 +// - protoc-gen-cpp-tableau-loader v0.12.0 // - protoc (unknown) // clang-format off diff --git a/test/cpp-tableau-loader/src/protoconf/logger.pc.cc b/test/cpp-tableau-loader/src/protoconf/logger.pc.cc index 2447b87..62fbb2c 100644 --- a/test/cpp-tableau-loader/src/protoconf/logger.pc.cc +++ b/test/cpp-tableau-loader/src/protoconf/logger.pc.cc @@ -1,6 +1,6 @@ // Code generated by protoc-gen-cpp-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-cpp-tableau-loader v0.11.0 +// - protoc-gen-cpp-tableau-loader v0.12.0 // - protoc (unknown) // clang-format off diff --git a/test/cpp-tableau-loader/src/protoconf/logger.pc.h b/test/cpp-tableau-loader/src/protoconf/logger.pc.h index 07d6884..21ee6cd 100644 --- a/test/cpp-tableau-loader/src/protoconf/logger.pc.h +++ b/test/cpp-tableau-loader/src/protoconf/logger.pc.h @@ -1,6 +1,6 @@ // Code generated by protoc-gen-cpp-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-cpp-tableau-loader v0.11.0 +// - protoc-gen-cpp-tableau-loader v0.12.0 // - protoc (unknown) // clang-format off diff --git a/test/cpp-tableau-loader/src/protoconf/patch_conf.pc.cc b/test/cpp-tableau-loader/src/protoconf/patch_conf.pc.cc index ff10e77..4408993 100644 --- a/test/cpp-tableau-loader/src/protoconf/patch_conf.pc.cc +++ b/test/cpp-tableau-loader/src/protoconf/patch_conf.pc.cc @@ -1,6 +1,6 @@ // Code generated by protoc-gen-cpp-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-cpp-tableau-loader v0.11.0 +// - protoc-gen-cpp-tableau-loader v0.12.0 // - protoc (unknown) // clang-format off // source: patch_conf.proto diff --git a/test/cpp-tableau-loader/src/protoconf/patch_conf.pc.h b/test/cpp-tableau-loader/src/protoconf/patch_conf.pc.h index a374135..69181d0 100644 --- a/test/cpp-tableau-loader/src/protoconf/patch_conf.pc.h +++ b/test/cpp-tableau-loader/src/protoconf/patch_conf.pc.h @@ -1,6 +1,6 @@ // Code generated by protoc-gen-cpp-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-cpp-tableau-loader v0.11.0 +// - protoc-gen-cpp-tableau-loader v0.12.0 // - protoc (unknown) // clang-format off // source: patch_conf.proto diff --git a/test/cpp-tableau-loader/src/protoconf/scheduler.pc.cc b/test/cpp-tableau-loader/src/protoconf/scheduler.pc.cc index 0820755..41c5d46 100644 --- a/test/cpp-tableau-loader/src/protoconf/scheduler.pc.cc +++ b/test/cpp-tableau-loader/src/protoconf/scheduler.pc.cc @@ -1,6 +1,6 @@ // Code generated by protoc-gen-cpp-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-cpp-tableau-loader v0.11.0 +// - protoc-gen-cpp-tableau-loader v0.12.0 // - protoc (unknown) // clang-format off diff --git a/test/cpp-tableau-loader/src/protoconf/scheduler.pc.h b/test/cpp-tableau-loader/src/protoconf/scheduler.pc.h index b2a1242..b217e48 100644 --- a/test/cpp-tableau-loader/src/protoconf/scheduler.pc.h +++ b/test/cpp-tableau-loader/src/protoconf/scheduler.pc.h @@ -1,6 +1,6 @@ // Code generated by protoc-gen-cpp-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-cpp-tableau-loader v0.11.0 +// - protoc-gen-cpp-tableau-loader v0.12.0 // - protoc (unknown) // clang-format off diff --git a/test/cpp-tableau-loader/src/protoconf/test_conf.pc.cc b/test/cpp-tableau-loader/src/protoconf/test_conf.pc.cc index a39c343..da60285 100644 --- a/test/cpp-tableau-loader/src/protoconf/test_conf.pc.cc +++ b/test/cpp-tableau-loader/src/protoconf/test_conf.pc.cc @@ -1,6 +1,6 @@ // Code generated by protoc-gen-cpp-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-cpp-tableau-loader v0.11.0 +// - protoc-gen-cpp-tableau-loader v0.12.0 // - protoc (unknown) // clang-format off // source: test_conf.proto diff --git a/test/cpp-tableau-loader/src/protoconf/test_conf.pc.h b/test/cpp-tableau-loader/src/protoconf/test_conf.pc.h index c2b92c4..b26cf3a 100644 --- a/test/cpp-tableau-loader/src/protoconf/test_conf.pc.h +++ b/test/cpp-tableau-loader/src/protoconf/test_conf.pc.h @@ -1,6 +1,6 @@ // Code generated by protoc-gen-cpp-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-cpp-tableau-loader v0.11.0 +// - protoc-gen-cpp-tableau-loader v0.12.0 // - protoc (unknown) // clang-format off // source: test_conf.proto diff --git a/test/cpp-tableau-loader/src/protoconf/util.pc.cc b/test/cpp-tableau-loader/src/protoconf/util.pc.cc index 7146119..4ae2cee 100644 --- a/test/cpp-tableau-loader/src/protoconf/util.pc.cc +++ b/test/cpp-tableau-loader/src/protoconf/util.pc.cc @@ -1,6 +1,6 @@ // Code generated by protoc-gen-cpp-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-cpp-tableau-loader v0.11.0 +// - protoc-gen-cpp-tableau-loader v0.12.0 // - protoc (unknown) // clang-format off diff --git a/test/cpp-tableau-loader/src/protoconf/util.pc.h b/test/cpp-tableau-loader/src/protoconf/util.pc.h index 8ef011c..b6ab1c9 100644 --- a/test/cpp-tableau-loader/src/protoconf/util.pc.h +++ b/test/cpp-tableau-loader/src/protoconf/util.pc.h @@ -1,6 +1,6 @@ // Code generated by protoc-gen-cpp-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-cpp-tableau-loader v0.11.0 +// - protoc-gen-cpp-tableau-loader v0.12.0 // - protoc (unknown) // clang-format off diff --git a/test/cpp-tableau-loader/tests/get_test.cpp b/test/cpp-tableau-loader/tests/get_test.cpp new file mode 100644 index 0000000..f6ae089 --- /dev/null +++ b/test/cpp-tableau-loader/tests/get_test.cpp @@ -0,0 +1,100 @@ +// Get block: point-lookup getters (map getters). Mirrors the same scenarios in: +// - Go: test/go-tableau-loader/get_test.go +// - C#: test/csharp-tableau-loader/tests/GetTests.cs +// - TS: test/ts-tableau-loader/tests/get.test.ts + +#include + +#include "protoconf/hero_conf.pc.h" +#include "protoconf/index_conf.pc.h" +#include "protoconf/item_conf.pc.h" +#include "tests/hub_fixture.h" + +namespace { + +// ---- ItemConf: 1st-level int-keyed map getter ---- + +TEST_F(HubFixture, ItemConf_Get) { + auto item = Hub::Instance().Get(); + ASSERT_NE(item, nullptr); + + // found + auto apple = item->Get(1); + ASSERT_NE(apple, nullptr); + EXPECT_EQ(apple->name(), "apple"); + auto coin = item->Get(0); + ASSERT_NE(coin, nullptr); + EXPECT_EQ(coin->name(), "coin1"); + + // not found + EXPECT_EQ(item->Get(12345), nullptr); +} + +// ---- ActivityConf: nested map getters with a 64-bit outer key ---- + +TEST_F(HubFixture, ActivityConf_Get) { + auto conf = Hub::Instance().Get(); + ASSERT_NE(conf, nullptr); + + auto activity = conf->Get(100001); + ASSERT_NE(activity, nullptr); + EXPECT_EQ(activity->activity_name(), "活动1"); + + auto chapter = conf->Get(100001, 1); + ASSERT_NE(chapter, nullptr); + EXPECT_EQ(chapter->chapter_name(), "签到活动章1"); + + auto section = conf->Get(100001, 1, 2); + ASSERT_NE(section, nullptr); + EXPECT_EQ(section->section_id(), 2u); + + // sectionRankMap["2001"] === 2 + auto rank = conf->Get(100001, 1, 2, 2001); + ASSERT_NE(rank, nullptr); + EXPECT_EQ(*rank, 2); +} + +TEST_F(HubFixture, ActivityConf_Get3_NotFound) { + auto conf = Hub::Instance().Get(); + ASSERT_NE(conf, nullptr); + EXPECT_EQ(conf->Get(100001, 1, 999), nullptr); +} + +// ---- ThemeConf: string-keyed map getter ---- + +TEST_F(HubFixture, ThemeConf_Get) { + auto theme = Hub::Instance().Get(); + ASSERT_NE(theme, nullptr); + EXPECT_GT(theme->Data().theme_map_size(), 0); +} + +// ---- HeroBaseConf ---- + +TEST_F(HubFixture, HeroBaseConf_Get) { + auto conf = Hub::Instance().Get(); + ASSERT_NE(conf, nullptr); + EXPECT_GT(conf->Data().hero_map_size(), 0); +} + +// ---- FruitConf: leveled point getters ---- + +TEST_F(HubFixture, FruitConf_Get) { + auto fruit = Hub::Instance().Get(); + ASSERT_NE(fruit, nullptr); + + // Get1: found / not found + ASSERT_NE(fruit->Get(kFruitTypeApple), nullptr); + EXPECT_EQ(fruit->Get(999), nullptr); + + // Get2: APPLE -> item 1001, price 10 + auto apple_item = fruit->Get(kFruitTypeApple, 1001); + ASSERT_NE(apple_item, nullptr); + EXPECT_EQ(apple_item->id(), 1001); + EXPECT_EQ(apple_item->price(), 10); + + // not found: wrong item id / wrong fruitType + EXPECT_EQ(fruit->Get(kFruitTypeApple, 9999), nullptr); + EXPECT_EQ(fruit->Get(999, 1001), nullptr); +} + +} // namespace diff --git a/test/cpp-tableau-loader/tests/hub_fixture.h b/test/cpp-tableau-loader/tests/hub_fixture.h new file mode 100644 index 0000000..bcbdadf --- /dev/null +++ b/test/cpp-tableau-loader/tests/hub_fixture.h @@ -0,0 +1,41 @@ +// Shared test fixture and constants for the C++ tableau loader tests. +// +// The unit tests are split into four blocks, one file each, mirroring the +// Go / C# / TS test layout: +// - load_test.cpp (Load, CustomConf, Bin, Patch) +// - get_test.cpp (point-lookup getters) +// - ordered_map_test.cpp (GetOrderedMap traversal) +// - index_test.cpp (Find* index finders) +#pragma once + +#include + +#include + +#include "hub/hub.h" +#include "protoconf/hub.pc.h" +#include "protoconf/test_conf.pc.h" +#include "tests/test_paths.h" + +// fruitType values match FruitConf.json (== protoconf::FruitType enum values). +constexpr int kFruitTypeApple = protoconf::FRUIT_TYPE_APPLE; +constexpr int kFruitTypeOrange = protoconf::FRUIT_TYPE_ORANGE; +constexpr int kFruitTypeBanana = protoconf::FRUIT_TYPE_BANANA; + +// HubFixture loads the whole hub once per test, so test order doesn't matter +// (Hub::Instance() is a process-wide singleton). Mirrors the shared prepareHub +// helper in Go and the HubFixture in C#. +class HubFixture : public ::testing::Test { + protected: + void SetUp() override { + Hub::Instance().InitOnce(); + auto options = std::make_shared(); + options->ignore_unknown_fields = true; + auto mopts = std::make_shared(); + mopts->path = (test::TestPaths::Conf() / "ItemConf.json").string(); + options->messager_options["ItemConf"] = mopts; + + bool ok = Hub::Instance().Load(test::TestPaths::Conf().string() + "/", tableau::Format::kJSON, options); + ASSERT_TRUE(ok) << "hub load failed: " << tableau::GetErrMsg(); + } +}; diff --git a/test/cpp-tableau-loader/tests/hub_test.cpp b/test/cpp-tableau-loader/tests/hub_test.cpp deleted file mode 100644 index 96d2a05..0000000 --- a/test/cpp-tableau-loader/tests/hub_test.cpp +++ /dev/null @@ -1,85 +0,0 @@ -// Unit tests for the C++ tableau loader, replacing the legacy print-based -// verification that previously lived in src/main.cpp. - -#include - -#include "hub/custom/item/custom_item_conf.h" -#include "hub/hub.h" -#include "protoconf/hub.pc.h" -#include "protoconf/item_conf.pc.h" -#include "protoconf/test_conf.pc.h" -#include "tests/test_paths.h" - -namespace { - -// HubFixture loads the hub once per test, so test order doesn't matter -// (Hub::Instance() is a process-wide singleton shared with PatchTest). -class HubFixture : public ::testing::Test { - protected: - void SetUp() override { - Hub::Instance().InitOnce(); - auto options = std::make_shared(); - options->ignore_unknown_fields = true; - auto mopts = std::make_shared(); - mopts->path = (test::TestPaths::Conf() / "ItemConf.json").string(); - options->messager_options["ItemConf"] = mopts; - - bool ok = Hub::Instance().Load(test::TestPaths::Conf().string() + "/", tableau::Format::kJSON, options); - ASSERT_TRUE(ok) << "hub load failed: " << tableau::GetErrMsg(); - } -}; - -// ---- ItemConf ---- - -TEST_F(HubFixture, ItemConf_FindFirstAwardItem_Found) { - auto item_mgr = Hub::Instance().Get(); - ASSERT_NE(item_mgr, nullptr); - auto item = item_mgr->FindFirstAwardItem(1, "apple"); - ASSERT_NE(item, nullptr) << "ItemConf FindFirstAwardItem(1, apple) failed"; -} - -TEST_F(HubFixture, ItemConf_FindItemInfoMap_NonEmpty) { - auto item_mgr = Hub::Instance().Get(); - ASSERT_NE(item_mgr, nullptr); - const auto& info_map = item_mgr->FindItemInfoMap(); - EXPECT_FALSE(info_map.empty()); -} - -// ---- ActivityConf ---- - -TEST_F(HubFixture, ActivityConf_GetOrderedMap_Chapter) { - const auto* chapter_ordered_map = - Hub::Instance().GetOrderedMap(100001); - ASSERT_NE(chapter_ordered_map, nullptr); - EXPECT_FALSE(chapter_ordered_map->empty()); -} - -TEST_F(HubFixture, ActivityConf_GetOrderedMap_Rank) { - const auto* rank_ordered_map = - Hub::Instance().GetOrderedMap(100001, 1, - 2); - ASSERT_NE(rank_ordered_map, nullptr); -} - -TEST_F(HubFixture, ActivityConf_FindChapter) { - auto activity_conf = Hub::Instance().Get(); - ASSERT_NE(activity_conf, nullptr); - - auto index_chapters = activity_conf->FindChapter(1); - ASSERT_NE(index_chapters, nullptr); - EXPECT_FALSE(index_chapters->empty()); - - auto first_chapter = activity_conf->FindFirstChapter(1); - ASSERT_NE(first_chapter, nullptr); -} - -// ---- CustomItemConf ---- - -TEST_F(HubFixture, CustomItemConf_SpecialItemNameResolved) { - auto custom = Hub::Instance().Get(); - ASSERT_NE(custom, nullptr); - EXPECT_FALSE(custom->GetSpecialItemName().empty()); -} - -} // namespace diff --git a/test/cpp-tableau-loader/tests/index_test.cpp b/test/cpp-tableau-loader/tests/index_test.cpp new file mode 100644 index 0000000..d729c0e --- /dev/null +++ b/test/cpp-tableau-loader/tests/index_test.cpp @@ -0,0 +1,150 @@ +// Index block: index finders (leveled, ordered and multi-column). Mirrors: +// - Go: test/go-tableau-loader/index_test.go +// - C#: test/csharp-tableau-loader/tests/IndexTests.cs +// - TS: test/ts-tableau-loader/tests/index.test.ts (cases 13-16) + +#include + +#include + +#include "protoconf/index_conf.pc.h" +#include "protoconf/item_conf.pc.h" +#include "tests/hub_fixture.h" + +namespace { + +// ---- FruitConf: leveled index (Price) finders ---- + +TEST_F(HubFixture, FruitConf_IndexFinders) { + auto fruit = Hub::Instance().Get(); + ASSERT_NE(fruit, nullptr); + + // global index: price -> items + auto items = fruit->FindItem(10); + ASSERT_NE(items, nullptr); + ASSERT_EQ(items->size(), 1u); + EXPECT_EQ((*items)[0]->id(), 1001); + ASSERT_NE(fruit->FindFirstItem(20), nullptr); + EXPECT_EQ(fruit->FindFirstItem(20)->id(), 1002); + EXPECT_EQ(fruit->FindItem(999), nullptr); + // 6 items, all unique prices -> 6 entries + EXPECT_EQ(fruit->FindItemMap().size(), 6u); + + // 1st-level scoped index: (fruitType) -> price -> items + auto orange = fruit->FindItem(kFruitTypeOrange, 15); + ASSERT_NE(orange, nullptr); + ASSERT_EQ(orange->size(), 1u); + EXPECT_EQ((*orange)[0]->id(), 2001); + ASSERT_NE(fruit->FindFirstItem(kFruitTypeBanana, 8), nullptr); + EXPECT_EQ(fruit->FindFirstItem(kFruitTypeBanana, 8)->id(), 3001); + ASSERT_NE(fruit->FindItemMap(kFruitTypeApple), nullptr); + EXPECT_EQ(fruit->FindItemMap(kFruitTypeApple)->size(), 2u); + EXPECT_EQ(fruit->FindItemMap(999), nullptr); + EXPECT_EQ(fruit->FindItem(999, 15), nullptr); +} + +// ---- FruitConf: ordered index (Price@OrderedFruit) finders ---- + +TEST_F(HubFixture, FruitConf_OrderedIndexFinders) { + auto fruit = Hub::Instance().Get(); + ASSERT_NE(fruit, nullptr); + + auto items = fruit->FindOrderedFruit(10); + ASSERT_NE(items, nullptr); + ASSERT_EQ(items->size(), 1u); + EXPECT_EQ((*items)[0]->id(), 1001); + ASSERT_NE(fruit->FindFirstOrderedFruit(25), nullptr); + EXPECT_EQ(fruit->FindFirstOrderedFruit(25)->id(), 2002); + EXPECT_EQ(fruit->FindOrderedFruit(999), nullptr); + + // ordered map is keyed in ascending price order (std::map iterates ascending). + const auto& m = fruit->FindOrderedFruitMap(); + EXPECT_EQ(m.size(), 6u); + int prev = std::numeric_limits::min(); + for (const auto& kv : m) { + EXPECT_GE(kv.first, prev) << "ordered map keys not ascending"; + prev = kv.first; + } + + // 1st-level scoped ordered index + auto orange = fruit->FindOrderedFruit(kFruitTypeOrange, 25); + ASSERT_NE(orange, nullptr); + ASSERT_EQ(orange->size(), 1u); + EXPECT_EQ((*orange)[0]->id(), 2002); + ASSERT_NE(fruit->FindFirstOrderedFruit(kFruitTypeBanana, 12), nullptr); + EXPECT_EQ(fruit->FindFirstOrderedFruit(kFruitTypeBanana, 12)->id(), 3002); + EXPECT_EQ(fruit->FindOrderedFruit(999, 25), nullptr); +} + +// ---- ItemConf: single-column index ---- + +TEST_F(HubFixture, ItemConf_FindItemInfoMap_NonEmpty) { + auto item = Hub::Instance().Get(); + ASSERT_NE(item, nullptr); + const auto& info_map = item->FindItemInfoMap(); + EXPECT_FALSE(info_map.empty()); +} + +// ---- ItemConf: multi-column (composite-key) indexes ---- + +TEST_F(HubFixture, ItemConf_ParamExtType_OrderedMultiColumnIndex) { + auto item = Hub::Instance().Get(); + ASSERT_NE(item, nullptr); + + // apple has param_list [1,2,3] x extTypeList [APPLE, ORANGE] -> 6 buckets, + // and it is the only item carrying both columns, so the map has 6 entries. + const auto& m = item->FindParamExtTypeMap(); + EXPECT_EQ(m.size(), 6u); + + // std::map iterates in ascending key order; each key round-trips through the finder. + int prev_param = std::numeric_limits::min(); + for (const auto& kv : m) { + EXPECT_GE(kv.first.param, prev_param) << "ParamExtType keys not ascending by param"; + prev_param = kv.first.param; + auto got = item->FindParamExtType(kv.first.param, kv.first.ext_type); + ASSERT_NE(got, nullptr); + EXPECT_EQ(got->size(), kv.second.size()); + } + + // a concrete known bucket: (param=1, extType=APPLE) -> apple + auto apple = item->FindParamExtType(1, protoconf::FRUIT_TYPE_APPLE); + ASSERT_NE(apple, nullptr); + ASSERT_EQ(apple->size(), 1u); + EXPECT_EQ((*apple)[0]->name(), "apple"); + EXPECT_EQ(item->FindParamExtType(999, protoconf::FRUIT_TYPE_APPLE), nullptr); +} + +TEST_F(HubFixture, ItemConf_AwardItem_MultiColumnIndex) { + auto item = Hub::Instance().Get(); + ASSERT_NE(item, nullptr); + + const auto& m = item->FindAwardItemMap(); + for (const auto& kv : m) { + auto got = item->FindAwardItem(kv.first.id, kv.first.name); + ASSERT_NE(got, nullptr); + EXPECT_EQ(got->size(), kv.second.size()); + } + + // apple is keyed by (id=1, name="apple") + auto apple = item->FindAwardItem(1, "apple"); + ASSERT_NE(apple, nullptr); + ASSERT_EQ(apple->size(), 1u); + EXPECT_EQ((*apple)[0]->id(), 1u); + ASSERT_NE(item->FindFirstAwardItem(1, "apple"), nullptr); +} + +// ---- ActivityConf: leveled index finder (C++-specific extra) ---- + +TEST_F(HubFixture, ActivityConf_FindChapter) { + auto activity_conf = Hub::Instance().Get(); + ASSERT_NE(activity_conf, nullptr); + + auto index_chapters = activity_conf->FindChapter(1); + ASSERT_NE(index_chapters, nullptr); + EXPECT_FALSE(index_chapters->empty()); + + auto first_chapter = activity_conf->FindFirstChapter(1); + ASSERT_NE(first_chapter, nullptr); +} + +} // namespace diff --git a/test/cpp-tableau-loader/tests/patch_test.cpp b/test/cpp-tableau-loader/tests/load_test.cpp similarity index 73% rename from test/cpp-tableau-loader/tests/patch_test.cpp rename to test/cpp-tableau-loader/tests/load_test.cpp index 2756193..d261be5 100644 --- a/test/cpp-tableau-loader/tests/patch_test.cpp +++ b/test/cpp-tableau-loader/tests/load_test.cpp @@ -1,19 +1,58 @@ -// Patch-loading tests for the C++ loader, mirroring: -// - Go: test/go-tableau-loader/main_test.go::Test_Patch -// - C#: test/csharp-tableau-loader/tests/PatchTests.cs +// Load block: hub loading, custom conf (ProcessAfterLoadAll), binary-format +// loading and patch loading. Mirrors the same scenarios in: +// - Go: test/go-tableau-loader/load_test.go +// - C#: test/csharp-tableau-loader/tests/LoadTests.cs +// - TS: test/ts-tableau-loader/tests/load.test.ts #include #include -#include "hub/hub.h" -#include "protoconf/hub.pc.h" +#include "hub/custom/item/custom_item_conf.h" +#include "protoconf/hero_conf.pc.h" +#include "protoconf/item_conf.pc.h" #include "protoconf/patch_conf.pc.h" -#include "tests/test_paths.h" +#include "tests/hub_fixture.h" namespace { using ::google::protobuf::util::MessageDifferencer; +// ---- Load ---- + +TEST_F(HubFixture, Load_AllMessagers_Succeeds) { + // SetUp() already asserts the hub loads; verify a few messagers are present. + EXPECT_NE(Hub::Instance().Get(), nullptr); + EXPECT_NE(Hub::Instance().Get(), nullptr); + EXPECT_NE(Hub::Instance().Get(), nullptr); +} + +// ---- CustomConf ---- + +TEST_F(HubFixture, CustomItemConf_ProcessAfterLoadAll_ResolvesSpecialItem) { + auto custom = Hub::Instance().Get(); + ASSERT_NE(custom, nullptr); + EXPECT_FALSE(custom->GetSpecialItemName().empty()); +} + +// ---- Bin ---- + +TEST_F(HubFixture, HeroConf_LoadFromBin_Succeeds) { + tableau::HeroConf hero_conf; + bool ok = hero_conf.Load(test::TestPaths::Bin().string() + "/", tableau::Format::kBin); + ASSERT_TRUE(ok) << "failed to load HeroConf.binpb: " << tableau::GetErrMsg(); +} + +TEST_F(HubFixture, HeroConf_LoadFromMissingDir_Fails) { + tableau::HeroConf hero_conf; + auto missing = (test::TestPaths::Testdata() / "notexist").string() + "/"; + EXPECT_FALSE(hero_conf.Load(missing, tableau::Format::kBin)); +} + +// ---- Patch ---- +// PatchTest uses its own fixture: it only initializes the registry (InitOnce) +// and loads a fresh hub per test with patch options, mirroring the C#/Go patch +// tests that construct a standalone hub. + class PatchTest : public ::testing::Test { protected: void SetUp() override { Hub::Instance().InitOnce(); } @@ -119,8 +158,9 @@ TEST_F(PatchTest, ModeOnlyPatch_AppliesPatchesFromEmpty) { ASSERT_TRUE(Hub::Instance().Load(test::TestPaths::Conf().string() + "/", tableau::Format::kJSON, options)); auto mgr = Hub::Instance().Get(); ASSERT_NE(mgr, nullptr); - // OnlyPatch starts from an empty message; Name must come from a patch file. - EXPECT_FALSE(mgr->Data().name().empty()); + // OnlyPatch starts from an empty message; Name must come from the patches. + // patchconf sets name="orange"; patchconf2 carries no name, so it survives. + EXPECT_EQ("orange", mgr->Data().name()); } } // namespace diff --git a/test/cpp-tableau-loader/tests/ordered_map_test.cpp b/test/cpp-tableau-loader/tests/ordered_map_test.cpp new file mode 100644 index 0000000..3fd984d --- /dev/null +++ b/test/cpp-tableau-loader/tests/ordered_map_test.cpp @@ -0,0 +1,91 @@ +// OrderedMap block: GetOrderedMap traversal. Mirrors the same scenarios in: +// - Go: test/go-tableau-loader/ordered_map_test.go +// - C#: test/csharp-tableau-loader/tests/OrderedMapTests.cs +// - TS: test/ts-tableau-loader/tests/ordered_map.test.ts + +#include + +#include + +#include "protoconf/hero_conf.pc.h" +#include "protoconf/item_conf.pc.h" +#include "tests/hub_fixture.h" + +namespace { + +// ---- ActivityConf: nested ordered map ---- +// getOrderedMap exposes the full sorted tree; each node carries its value +// (.second) and the next-level sub-map (.first). std::map iterates ascending. + +TEST_F(HubFixture, ActivityConf_GetOrderedMap_Traverses) { + auto conf = Hub::Instance().Get(); + ASSERT_NE(conf, nullptr); + + const auto* ordered_map = conf->GetOrderedMap(); + ASSERT_NE(ordered_map, nullptr); + EXPECT_FALSE(ordered_map->empty()); + + // 1st level: keys ascending; node.second is the Activity value. + uint64_t prev = 0; + for (const auto& kv : *ordered_map) { + EXPECT_GE(kv.first, prev) << "ordered map keys not ascending"; + prev = kv.first; + + // 2nd level sub-map is reachable and non-empty (every activity has chapters). + const auto& chapter_ordered_map = kv.second.first; + EXPECT_FALSE(chapter_ordered_map.empty()) << "activity " << kv.first << " has no chapters"; + for (const auto& kv2 : chapter_ordered_map) { + (void)kv2.first; + (void)kv2.second.second; + } + } +} + +// Hub::GetOrderedMap(leveled-keys): the typed, key-scoped overloads that +// return a specific sub-tree level directly (1-key -> chapter map, 3-key -> rank +// map). These complement the no-arg conf->GetOrderedMap() traversal above. + +TEST_F(HubFixture, ActivityConf_GetOrderedMap_Chapter) { + const auto* chapter_ordered_map = + Hub::Instance().GetOrderedMap(100001); + ASSERT_NE(chapter_ordered_map, nullptr); + EXPECT_FALSE(chapter_ordered_map->empty()); +} + +TEST_F(HubFixture, ActivityConf_GetOrderedMap_Rank) { + const auto* rank_ordered_map = + Hub::Instance().GetOrderedMap(100001, 1, + 2); + ASSERT_NE(rank_ordered_map, nullptr); +} + +// ---- ItemConf: 1st-level ordered map ---- + +TEST_F(HubFixture, ItemConf_GetOrderedMap_Ascending) { + auto item = Hub::Instance().Get(); + ASSERT_NE(item, nullptr); + + const auto* ordered_map = item->GetOrderedMap(); + ASSERT_NE(ordered_map, nullptr); + EXPECT_FALSE(ordered_map->empty()); + + // keys ascending by id; prev starts at 0 so iteration 1 (kv.first >= 0) holds. + uint32_t prev = 0; + for (const auto& kv : *ordered_map) { + EXPECT_GE(kv.first, prev) << "ItemConf ordered map keys not ascending"; + prev = kv.first; + } +} + +// ---- HeroConf: ordered map accessible ---- + +TEST_F(HubFixture, HeroConf_GetOrderedMap_Accessible) { + auto hero_conf = Hub::Instance().Get(); + ASSERT_NE(hero_conf, nullptr); + + const auto* hero_ordered_map = hero_conf->GetOrderedMap(); + ASSERT_NE(hero_ordered_map, nullptr); +} + +} // namespace diff --git a/test/cpp-tableau-loader/tests/test_paths.h b/test/cpp-tableau-loader/tests/test_paths.h index c94db4a..5e3b195 100644 --- a/test/cpp-tableau-loader/tests/test_paths.h +++ b/test/cpp-tableau-loader/tests/test_paths.h @@ -15,6 +15,7 @@ class TestPaths { } static std::filesystem::path Conf() { return Testdata() / "conf"; } + static std::filesystem::path Bin() { return Testdata() / "bin"; } static std::filesystem::path PatchConf() { return Testdata() / "patchconf"; } static std::filesystem::path PatchConf2() { return Testdata() / "patchconf2"; } static std::filesystem::path PatchResult() { return Testdata() / "patchresult"; } diff --git a/test/csharp-tableau-loader/tests/ActivityConfTests.cs b/test/csharp-tableau-loader/tests/ActivityConfTests.cs deleted file mode 100644 index 2f33575..0000000 --- a/test/csharp-tableau-loader/tests/ActivityConfTests.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Xunit; - -namespace LoaderTests -{ - [Collection("HubCollection")] - public class ActivityConfTests - { - private readonly Tableau.Hub _hub; - - public ActivityConfTests(HubFixture fixture) - { - _hub = fixture.Hub; - } - - [Fact] - public void Get3_Found_ReturnsSection() - { - var conf = _hub.GetActivityConf(); - Assert.NotNull(conf); - var section = conf!.Get3(100001, 1, 2); - Assert.NotNull(section); - Assert.Equal(2u, section!.SectionId); - } - - [Fact] - public void Get3_NotFound_ReturnsNull() - { - var conf = _hub.GetActivityConf(); - Assert.NotNull(conf); - var notFound = conf!.Get3(100001, 1, 999); - Assert.Null(notFound); - } - - [Fact] - public void GetOrderedMap_Traverses_NonEmpty() - { - var conf = _hub.GetActivityConf(); - Assert.NotNull(conf); - var orderedMap = conf!.GetOrderedMap(); - Assert.NotNull(orderedMap); - Assert.NotEmpty(orderedMap); - - int activities = 0; - foreach (var activityPair in orderedMap) - { - activities++; - var chapterOrderedMap = activityPair.Value.Item1; - Assert.NotNull(chapterOrderedMap); - } - Assert.True(activities > 0, "expected at least one activity"); - } - } -} diff --git a/test/csharp-tableau-loader/tests/BinTests.cs b/test/csharp-tableau-loader/tests/BinTests.cs deleted file mode 100644 index 41f596b..0000000 --- a/test/csharp-tableau-loader/tests/BinTests.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.IO; -using Xunit; - -namespace LoaderTests -{ - [Collection("HubCollection")] - public class BinTests - { - // Depending on HubFixture guarantees Tableau.Registry.Init() has run - // exactly once before any test in this class executes, and the - // collection serialization prevents concurrent registry mutation. - public BinTests(HubFixture _) { } - - [Fact] - public void HeroConf_LoadFromBin_Succeeds() - { - var heroConf = new Tableau.HeroConf(); - bool ok = heroConf.Load(TestPaths.BinDir, Tableau.Format.Bin); - Assert.True(ok, $"failed to load HeroConf.binpb: {Tableau.Util.GetErrMsg()}"); - Assert.NotNull(heroConf.Data()); - } - - [Fact] - public void HeroConf_LoadFromMissingDir_Fails() - { - var heroConf = new Tableau.HeroConf(); - string missingDir = Path.Combine(TestPaths.TestdataDir, "notexist"); - bool ok = heroConf.Load(missingDir, Tableau.Format.Bin); - Assert.False(ok); - } - } -} diff --git a/test/csharp-tableau-loader/tests/GetTests.cs b/test/csharp-tableau-loader/tests/GetTests.cs new file mode 100644 index 0000000..735e2dc --- /dev/null +++ b/test/csharp-tableau-loader/tests/GetTests.cs @@ -0,0 +1,99 @@ +using Xunit; + +namespace LoaderTests +{ + /// + /// Get block: point-lookup getters (map getters). Mirrors the same scenarios in: + /// - Go: test/go-tableau-loader/get_test.go + /// - C++: test/cpp-tableau-loader/tests/get_test.cpp + /// - TS: test/ts-tableau-loader/tests/get.test.ts + /// + [Collection("HubCollection")] + public class GetTests + { + private readonly Tableau.Hub _hub; + + public GetTests(HubFixture fixture) + { + _hub = fixture.Hub; + } + + [Fact] + public void GetItemConf_TypedAndGenericReturnSameInstance() + { + var itemConf1 = _hub.Get(); + var itemConf2 = _hub.GetItemConf(); + Assert.NotNull(itemConf1); + Assert.Same(itemConf1, itemConf2); + } + + // ---- ItemConf: 1st-level int-keyed map getter ---- + + [Fact] + public void ItemConf_Get1() + { + var item = _hub.GetItemConf(); + Assert.NotNull(item); + Assert.Equal("apple", item!.Get1(1)!.Name); + Assert.Equal("coin1", item.Get1(0)!.Name); + Assert.Null(item.Get1(12345)); + } + + // ---- ActivityConf: nested map getters with a 64-bit outer key ---- + + [Fact] + public void ActivityConf_Get() + { + var conf = _hub.GetActivityConf(); + Assert.NotNull(conf); + + Assert.Equal("活动1", conf!.Get1(100001)!.ActivityName); + Assert.Equal("签到活动章1", conf.Get2(100001, 1)!.ChapterName); + + var section = conf.Get3(100001, 1, 2); + Assert.NotNull(section); + Assert.Equal(2u, section!.SectionId); + + // sectionRankMap["2001"] === 2 + Assert.Equal(2, conf.Get4(100001, 1, 2, 2001)); + } + + [Fact] + public void ActivityConf_Get3_NotFound_ReturnsNull() + { + var conf = _hub.GetActivityConf(); + Assert.NotNull(conf); + Assert.Null(conf!.Get3(100001, 1, 999)); + } + + // ---- ThemeConf: string-keyed map getter ---- + + [Fact] + public void ThemeConf_Get1() + { + var theme = _hub.GetThemeConf(); + Assert.NotNull(theme); + Assert.NotEmpty(theme!.Data().ThemeMap); + } + + // ---- FruitConf: leveled point getters ---- + + [Fact] + public void FruitConf_Get1_Get2() + { + var fruit = _hub.GetFruitConf(); + Assert.NotNull(fruit); + + Assert.NotNull(fruit!.Get1(FruitTypes.Apple)); + Assert.Null(fruit.Get1(999)); + + // APPLE -> item 1001, price 10 + var item = fruit.Get2(FruitTypes.Apple, 1001); + Assert.NotNull(item); + Assert.Equal(1001, item!.Id); + Assert.Equal(10, item.Price); + Assert.Null(fruit.Get2(FruitTypes.Apple, 9999)); + Assert.Null(fruit.Get2(999, 1001)); + } + } +} diff --git a/test/csharp-tableau-loader/tests/HubFixture.cs b/test/csharp-tableau-loader/tests/HubFixture.cs index 1bfee07..575dcc9 100644 --- a/test/csharp-tableau-loader/tests/HubFixture.cs +++ b/test/csharp-tableau-loader/tests/HubFixture.cs @@ -4,6 +4,18 @@ namespace LoaderTests { + /// + /// fruitType values match FruitConf.json (== Protoconf.FruitType enum + /// values). Centralized here so the Get and Index test blocks share a + /// single definition, mirroring C++ (hub_fixture.h) and Go (index_test.go). + /// + public static class FruitTypes + { + public const int Apple = (int)Protoconf.FruitType.Apple; + public const int Orange = (int)Protoconf.FruitType.Orange; + public const int Banana = (int)Protoconf.FruitType.Banana; + } + /// /// Common test paths. xUnit runs tests from the build output directory /// (e.g. bin/Debug/net8.0/), so we resolve testdata relative to the source diff --git a/test/csharp-tableau-loader/tests/HubTests.cs b/test/csharp-tableau-loader/tests/HubTests.cs deleted file mode 100644 index 92e7061..0000000 --- a/test/csharp-tableau-loader/tests/HubTests.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Xunit; - -namespace LoaderTests -{ - [Collection("HubCollection")] - public class HubTests - { - private readonly Tableau.Hub _hub; - - public HubTests(HubFixture fixture) - { - _hub = fixture.Hub; - } - - [Fact] - public void TaskConf_FilteredOut_IsNull() - { - // HubFixture filters out TaskConf via HubOptions.Filter. - var taskConf = _hub.Get(); - Assert.Null(taskConf); - } - - [Fact] - public void HeroConf_LoadedAndOrderedMapAccessible() - { - var heroConf = _hub.Get(); - Assert.NotNull(heroConf); - Assert.NotNull(heroConf!.Data()); - - var heroOrderedMap = heroConf.GetOrderedMap(); - Assert.NotNull(heroOrderedMap); - } - - [Fact] - public void GetItemConf_TypedAndGenericReturnSameInstance() - { - var itemConf1 = _hub.Get(); - var itemConf2 = _hub.GetItemConf(); - Assert.NotNull(itemConf1); - Assert.Same(itemConf1, itemConf2); - } - - [Fact] - public void CustomItemConf_ProcessAfterLoadAll_ResolvesSpecialItem() - { - var customItemConf = _hub.Get(); - Assert.NotNull(customItemConf); - Assert.False(string.IsNullOrEmpty(customItemConf!.GetSpecialItemName())); - } - - [Fact] - public void ItemConf_FindItemInfoMap_NonEmpty() - { - var itemConf = _hub.GetItemConf(); - Assert.NotNull(itemConf); - var itemInfoMap = itemConf!.FindItemInfoMap(); - Assert.NotNull(itemInfoMap); - Assert.NotEmpty(itemInfoMap); - } - } -} diff --git a/test/csharp-tableau-loader/tests/IndexTests.cs b/test/csharp-tableau-loader/tests/IndexTests.cs new file mode 100644 index 0000000..35105aa --- /dev/null +++ b/test/csharp-tableau-loader/tests/IndexTests.cs @@ -0,0 +1,139 @@ +using Xunit; + +namespace LoaderTests +{ + /// + /// Index block: index finders (leveled, ordered and multi-column). Mirrors: + /// - Go: test/go-tableau-loader/index_test.go + /// - C++: test/cpp-tableau-loader/tests/index_test.cpp + /// - TS: test/ts-tableau-loader/tests/index.test.ts (cases 13-16) + /// + [Collection("HubCollection")] + public class IndexTests + { + private readonly Tableau.Hub _hub; + + public IndexTests(HubFixture fixture) + { + _hub = fixture.Hub; + } + + // ---- FruitConf: leveled index (Price) finders ---- + + [Fact] + public void FruitConf_IndexFinders() + { + var fruit = _hub.GetFruitConf(); + Assert.NotNull(fruit); + + // global index: price -> items + var items = fruit!.FindItem(10); + Assert.NotNull(items); + Assert.Single(items!); + Assert.Equal(1001, items![0].Id); + Assert.Equal(1002, fruit.FindFirstItem(20)!.Id); + Assert.Null(fruit.FindItem(999)); + // 6 items, all unique prices -> 6 entries + Assert.Equal(6, fruit.FindItemMap().Count); + + // 1st-level scoped index: (fruitType) -> price -> items + Assert.Equal(2001, fruit.FindItem1(FruitTypes.Orange, 15)![0].Id); + Assert.Equal(3001, fruit.FindFirstItem1(FruitTypes.Banana, 8)!.Id); + Assert.Equal(2, fruit.FindItemMap1(FruitTypes.Apple)!.Count); + Assert.Null(fruit.FindItemMap1(999)); + Assert.Null(fruit.FindItem1(999, 15)); + } + + // ---- FruitConf: ordered index (Price@OrderedFruit) finders ---- + + [Fact] + public void FruitConf_OrderedIndexFinders() + { + var fruit = _hub.GetFruitConf(); + Assert.NotNull(fruit); + + Assert.Equal(1001, fruit!.FindOrderedFruit(10)![0].Id); + Assert.Equal(2002, fruit.FindFirstOrderedFruit(25)!.Id); + Assert.Null(fruit.FindOrderedFruit(999)); + + // ordered map is keyed in ascending price order (SortedDictionary). + var m = fruit.FindOrderedFruitMap(); + Assert.Equal(6, m.Count); + int prev = int.MinValue; + foreach (var key in m.Keys) + { + Assert.True(key >= prev, $"ordered map keys not ascending: {key} after {prev}"); + prev = key; + } + + // 1st-level scoped ordered index + Assert.Equal(2002, fruit.FindOrderedFruit1(FruitTypes.Orange, 25)![0].Id); + Assert.Equal(3002, fruit.FindFirstOrderedFruit1(FruitTypes.Banana, 12)!.Id); + Assert.Null(fruit.FindOrderedFruit1(999, 25)); + } + + // ---- ItemConf: single-column index ---- + + [Fact] + public void ItemConf_FindItemInfoMap_NonEmpty() + { + var itemConf = _hub.GetItemConf(); + Assert.NotNull(itemConf); + var itemInfoMap = itemConf!.FindItemInfoMap(); + Assert.NotNull(itemInfoMap); + Assert.NotEmpty(itemInfoMap); + } + + // ---- ItemConf: multi-column (composite-key) indexes ---- + + [Fact] + public void ItemConf_ParamExtType_OrderedMultiColumnIndex() + { + var item = _hub.GetItemConf(); + Assert.NotNull(item); + + // apple has param_list [1,2,3] x extTypeList [APPLE, ORANGE] -> 6 buckets, + // and it is the only item carrying both columns, so the map has 6 entries. + var m = item!.FindParamExtTypeMap(); + Assert.Equal(6, m.Count); + + // SortedDictionary iterates in ascending key order; each key round-trips + // through the point-lookup finder. + int prevParam = int.MinValue; + foreach (var kv in m) + { + Assert.True(kv.Key.Param >= prevParam, "ParamExtType keys not ascending by param"); + prevParam = kv.Key.Param; + Assert.Equal(kv.Value, item.FindParamExtType(kv.Key.Param, kv.Key.ExtType)); + } + + // a concrete known bucket: (param=1, extType=APPLE) -> apple + var apple = item.FindParamExtType(1, Protoconf.FruitType.Apple); + Assert.NotNull(apple); + Assert.Single(apple!); + Assert.Equal("apple", apple![0].Name); + Assert.Null(item.FindParamExtType(999, Protoconf.FruitType.Apple)); + } + + [Fact] + public void ItemConf_AwardItem_MultiColumnIndex() + { + var item = _hub.GetItemConf(); + Assert.NotNull(item); + + var m = item!.FindAwardItemMap(); + foreach (var kv in m) + { + Assert.Equal(kv.Value, item.FindAwardItem(kv.Key.Id, kv.Key.Name)); + } + + // apple is keyed by (id=1, name="apple") + var apple = item.FindAwardItem(1, "apple"); + Assert.NotNull(apple); + Assert.Single(apple!); + Assert.Equal(1u, apple![0].Id); + // FindFirst* variant returns the first match for the same key. + Assert.NotNull(item.FindFirstAwardItem(1, "apple")); + } + } +} diff --git a/test/csharp-tableau-loader/tests/PatchTests.cs b/test/csharp-tableau-loader/tests/LoadTests.cs similarity index 70% rename from test/csharp-tableau-loader/tests/PatchTests.cs rename to test/csharp-tableau-loader/tests/LoadTests.cs index e9e3caa..af6083b 100644 --- a/test/csharp-tableau-loader/tests/PatchTests.cs +++ b/test/csharp-tableau-loader/tests/LoadTests.cs @@ -5,17 +5,71 @@ namespace LoaderTests { /// - /// Patch-loading tests, mirroring the same scenarios in: - /// - Go: test/go-tableau-loader/main_test.go::Test_Patch - /// - C++: test/cpp-tableau-loader/tests/patch_test.cpp + /// Load block: hub loading, filtering, custom conf (ProcessAfterLoadAll), + /// binary-format loading and patch loading. Mirrors the same scenarios in: + /// - Go: test/go-tableau-loader/load_test.go + /// - C++: test/cpp-tableau-loader/tests/load_test.cpp + /// - TS: test/ts-tableau-loader/tests/load.test.ts /// [Collection("HubCollection")] - public class PatchTests + public class LoadTests { - // Depending on HubFixture guarantees Tableau.Registry.Init() has run - // exactly once before any test in this class executes, and the - // collection serialization prevents concurrent registry mutation. - public PatchTests(HubFixture _) { } + private readonly Tableau.Hub _hub; + + public LoadTests(HubFixture fixture) + { + _hub = fixture.Hub; + } + + // ---- Load & Filter ---- + + [Fact] + public void Load_AllMessagers_Succeeds() + { + var map = _hub.GetMessagerMap(); + Assert.NotNull(map); + Assert.NotEmpty(map); + } + + [Fact] + public void TaskConf_FilteredOut_IsNull() + { + // HubFixture filters out TaskConf via HubOptions.Filter. + var taskConf = _hub.Get(); + Assert.Null(taskConf); + } + + // ---- CustomConf ---- + + [Fact] + public void CustomItemConf_ProcessAfterLoadAll_ResolvesSpecialItem() + { + var customItemConf = _hub.Get(); + Assert.NotNull(customItemConf); + Assert.False(string.IsNullOrEmpty(customItemConf!.GetSpecialItemName())); + } + + // ---- Bin ---- + + [Fact] + public void HeroConf_LoadFromBin_Succeeds() + { + var heroConf = new Tableau.HeroConf(); + bool ok = heroConf.Load(TestPaths.BinDir, Tableau.Format.Bin); + Assert.True(ok, $"failed to load HeroConf.binpb: {Tableau.Util.GetErrMsg()}"); + Assert.NotNull(heroConf.Data()); + } + + [Fact] + public void HeroConf_LoadFromMissingDir_Fails() + { + var heroConf = new Tableau.HeroConf(); + string missingDir = Path.Combine(TestPaths.TestdataDir, "notexist"); + bool ok = heroConf.Load(missingDir, Tableau.Format.Bin); + Assert.False(ok); + } + + // ---- Patch ---- [Fact] public void PatchConf_RecursivePatchConf_MatchesExpectedResult() @@ -58,7 +112,7 @@ public void PatchConf_PatchReplaceConf_ReplacesEntirely() } [Fact] - public void PatchConf2_DifferentFormat_PatchPathsOverride() + public void PatchConf2_PatchPathsOverride_UsesJson() { var hub = new Tableau.Hub(); var options = new Tableau.Load.Options @@ -69,10 +123,10 @@ public void PatchConf2_DifferentFormat_PatchPathsOverride() { ["PatchMergeConf"] = new Tableau.Load.MessagerOptions { - // .txtpb override (note: C# loader currently supports JSON/Bin only; - // this test validates that PatchPaths is honored even though the - // unmarshal step would surface an error for unsupported formats.) - // We instead point to .json to keep the format-supported path. + // The C++/Go mirrors override with a .txtpb path, but the C# + // loader currently supports JSON/Bin only, so this test points + // PatchPaths at the .json file instead. It still validates that + // a MessagerOptions.PatchPaths override is honored. PatchPaths = new List { Path.Combine(TestPaths.PatchConf2Dir, "PatchMergeConf.json"), @@ -167,8 +221,10 @@ public void PatchConf_ModeOnlyPatch_AppliesPatchesFromEmpty() Assert.True(hub.Load(TestPaths.ConfDir, Tableau.Format.JSON, options)); var data = hub.GetPatchMergeConf()!.Data(); - // OnlyPatch starts from an empty message, so Name must come from a patch file. - Assert.False(string.IsNullOrEmpty(data.Name)); + // OnlyPatch starts from an empty message; Name must come from the + // patches: patchconf sets name="orange"; patchconf2 carries no name, + // so it survives. + Assert.Equal("orange", data.Name); } } } diff --git a/test/csharp-tableau-loader/tests/OrderedMapTests.cs b/test/csharp-tableau-loader/tests/OrderedMapTests.cs new file mode 100644 index 0000000..bf09a6a --- /dev/null +++ b/test/csharp-tableau-loader/tests/OrderedMapTests.cs @@ -0,0 +1,77 @@ +using Xunit; + +namespace LoaderTests +{ + /// + /// OrderedMap block: GetOrderedMap traversal. Mirrors the same scenarios in: + /// - Go: test/go-tableau-loader/ordered_map_test.go + /// - C++: test/cpp-tableau-loader/tests/ordered_map_test.cpp + /// - TS: test/ts-tableau-loader/tests/ordered_map.test.ts + /// + [Collection("HubCollection")] + public class OrderedMapTests + { + private readonly Tableau.Hub _hub; + + public OrderedMapTests(HubFixture fixture) + { + _hub = fixture.Hub; + } + + // ---- ActivityConf: nested ordered map ---- + + [Fact] + public void ActivityConf_GetOrderedMap_Traverses_NonEmpty() + { + var conf = _hub.GetActivityConf(); + Assert.NotNull(conf); + var orderedMap = conf!.GetOrderedMap(); + Assert.NotNull(orderedMap); + Assert.NotEmpty(orderedMap); + + int activities = 0; + foreach (var activityPair in orderedMap) + { + activities++; + var chapterOrderedMap = activityPair.Value.Item1; + Assert.NotNull(chapterOrderedMap); + // 2nd-level sub-map is reachable and non-empty (every activity + // has chapters). + Assert.NotEmpty(chapterOrderedMap); + } + Assert.True(activities > 0, "expected at least one activity"); + } + + // ---- ItemConf: 1st-level ordered map ---- + + [Fact] + public void ItemConf_GetOrderedMap_Ascending() + { + var item = _hub.GetItemConf(); + Assert.NotNull(item); + var orderedMap = item!.GetOrderedMap(); + Assert.NotEmpty(orderedMap); + + // keys ascending by id; prev starts at 0 so iteration 1 (key >= 0) holds. + uint prev = uint.MinValue; + foreach (var key in orderedMap.Keys) + { + Assert.True(key >= prev, $"ItemConf ordered map keys not ascending: {key} after {prev}"); + prev = key; + } + } + + // ---- HeroConf: ordered map accessible ---- + + [Fact] + public void HeroConf_GetOrderedMap_Accessible() + { + var heroConf = _hub.Get(); + Assert.NotNull(heroConf); + Assert.NotNull(heroConf!.Data()); + + var heroOrderedMap = heroConf.GetOrderedMap(); + Assert.NotNull(heroOrderedMap); + } + } +} diff --git a/test/go-tableau-loader/get_test.go b/test/go-tableau-loader/get_test.go new file mode 100644 index 0000000..7a8b343 --- /dev/null +++ b/test/go-tableau-loader/get_test.go @@ -0,0 +1,186 @@ +package loader_test + +import ( + "errors" + "testing" + + "github.com/tableauio/loader/test/go-tableau-loader/protoconf/loader" +) + +// ---- ItemConf: 1st-level int-keyed map getter ---- + +func Test_ItemConf_Get1(t *testing.T) { + h := prepareHub(t) + conf := h.GetItemConf() + if conf == nil { + t.Fatal("ItemConf is nil") + } + + // found + item, err := conf.Get1(1) + if err != nil { + t.Fatalf("Get1(1) unexpected error: %v", err) + } + if item.GetName() != "apple" { + t.Errorf("Get1(1): expected name=apple, got %s", item.GetName()) + } + item, err = conf.Get1(0) + if err != nil { + t.Fatalf("Get1(0) unexpected error: %v", err) + } + if item.GetName() != "coin1" { + t.Errorf("Get1(0): expected name=coin1, got %s", item.GetName()) + } + + // not found + if _, err := conf.Get1(12345); !errors.Is(err, loader.ErrNotFound) { + t.Fatalf("Get1(12345): expected ErrNotFound, got: %v", err) + } +} + +// ---- ActivityConf: nested map getters with a 64-bit outer key ---- + +func Test_ActivityConf_Get(t *testing.T) { + h := prepareHub(t) + conf := h.GetActivityConf() + if conf == nil { + t.Fatal("ActivityConf is nil") + } + + activity, err := conf.Get1(100001) + if err != nil { + t.Fatalf("Get1(100001) unexpected error: %v", err) + } + if activity.GetActivityName() != "活动1" { + t.Errorf("Get1(100001): expected activityName=活动1, got %s", activity.GetActivityName()) + } + + chapter, err := conf.Get2(100001, 1) + if err != nil { + t.Fatalf("Get2(100001, 1) unexpected error: %v", err) + } + if chapter.GetChapterName() != "签到活动章1" { + t.Errorf("Get2(100001, 1): expected chapterName=签到活动章1, got %s", chapter.GetChapterName()) + } + + section, err := conf.Get3(100001, 1, 2) + if err != nil { + t.Fatalf("Get3(100001, 1, 2) unexpected error: %v", err) + } + if section.GetSectionId() != 2 { + t.Errorf("Get3(100001, 1, 2): expected sectionId=2, got %d", section.GetSectionId()) + } + + // sectionRankMap["2001"] === 2 + rank, err := conf.Get4(100001, 1, 2, 2001) + if err != nil { + t.Fatalf("Get4(100001, 1, 2, 2001) unexpected error: %v", err) + } + if rank != 2 { + t.Errorf("Get4(100001, 1, 2, 2001): expected 2, got %d", rank) + } +} + +func Test_ActivityConf_NotFound(t *testing.T) { + h := prepareHub(t) + conf := h.GetActivityConf() + if conf == nil { + t.Fatal("ActivityConf is nil") + } + if _, err := conf.Get3(100001, 1, 999); !errors.Is(err, loader.ErrNotFound) { + t.Fatalf("Get3(100001, 1, 999): expected ErrNotFound, got: %v", err) + } +} + +// ---- ThemeConf: string-keyed map getter ---- + +func Test_ThemeConf_Get1(t *testing.T) { + h := prepareHub(t) + conf := h.GetThemeConf() + if conf == nil { + t.Fatal("ThemeConf is nil") + } + if len(conf.Data().GetThemeMap()) == 0 { + t.Fatal("ThemeConf: themeMap is empty") + } +} + +// ---- HeroBaseConf ---- + +func Test_HeroBaseConf(t *testing.T) { + h := prepareHub(t) + conf := h.GetHeroBaseConf() + if conf == nil { + t.Fatal("HeroBaseConf is nil") + } + if len(conf.Data().GetHeroMap()) == 0 { + t.Fatal("HeroBaseConf: heroMap is empty") + } +} + +// ---- FruitConf: leveled point getters ---- + +func Test_FruitConf_Get1(t *testing.T) { + h := prepareHub(t) + conf := h.GetFruitConf() + + // found + fruit, err := conf.Get1(fruitTypeApple) + if err != nil { + t.Fatalf("Get1(%d) unexpected error: %v", fruitTypeApple, err) + } + if fruit == nil { + t.Fatal("Get1: returned nil fruit") + } + + // not found + if _, err := conf.Get1(999); !errors.Is(err, loader.ErrNotFound) { + t.Fatalf("Get1(999): expected ErrNotFound, got: %v", err) + } +} + +func Test_FruitConf_Get2(t *testing.T) { + h := prepareHub(t) + conf := h.GetFruitConf() + + // found: APPLE -> item 1001 + item, err := conf.Get2(fruitTypeApple, 1001) + if err != nil { + t.Fatalf("Get2(%d, 1001) unexpected error: %v", fruitTypeApple, err) + } + if item.GetId() != 1001 { + t.Errorf("Get2: expected id=1001, got %d", item.GetId()) + } + if item.GetPrice() != 10 { + t.Errorf("Get2: expected price=10, got %d", item.GetPrice()) + } + + // not found: wrong item id + if _, err := conf.Get2(fruitTypeApple, 9999); !errors.Is(err, loader.ErrNotFound) { + t.Fatalf("Get2(apple, 9999): expected ErrNotFound, got: %v", err) + } + + // not found: wrong fruitType + if _, err := conf.Get2(999, 1001); !errors.Is(err, loader.ErrNotFound) { + t.Fatalf("Get2(999, 1001): expected ErrNotFound, got: %v", err) + } +} + +func Test_Fruit6Conf_Get1(t *testing.T) { + h := prepareHub(t) + conf := h.GetFruit6Conf() + + // found + fruit, err := conf.Get1(fruitTypeApple) + if err != nil { + t.Fatalf("Get1(%d) unexpected error: %v", fruitTypeApple, err) + } + if fruit == nil { + t.Fatal("Get1: returned nil fruit") + } + + // not found + if _, err := conf.Get1(999); !errors.Is(err, loader.ErrNotFound) { + t.Fatalf("Get1(999): expected ErrNotFound, got: %v", err) + } +} diff --git a/test/go-tableau-loader/index_test.go b/test/go-tableau-loader/index_test.go index cd60bbb..0b7a4ec 100644 --- a/test/go-tableau-loader/index_test.go +++ b/test/go-tableau-loader/index_test.go @@ -1,76 +1,21 @@ package loader_test import ( - "errors" "testing" "github.com/tableauio/loader/test/go-tableau-loader/protoconf" "github.com/tableauio/loader/test/go-tableau-loader/protoconf/loader" ) -// fruitType constants matching FruitConf.json / Fruit6Conf.json +// fruitType constants matching FruitConf.json / Fruit6Conf.json. Shared by the +// get and index test blocks. var ( fruitTypeApple = int32(protoconf.FruitType_FRUIT_TYPE_APPLE) fruitTypeOrange = int32(protoconf.FruitType_FRUIT_TYPE_ORANGE) fruitTypeBanana = int32(protoconf.FruitType_FRUIT_TYPE_BANANA) ) -// ---- FruitConf ---- - -func Test_FruitConf_Get1(t *testing.T) { - h := prepareHub(t) - conf := h.GetFruitConf() - - // found - fruit, err := conf.Get1(fruitTypeApple) - if err != nil { - t.Fatalf("Get1(%d) unexpected error: %v", fruitTypeApple, err) - } - if fruit == nil { - t.Fatal("Get1: returned nil fruit") - } - - // not found - _, err = conf.Get1(999) - if err == nil { - t.Fatal("Get1(999): expected ErrNotFound, got nil") - } - if !errors.Is(err, loader.ErrNotFound) { - t.Fatalf("Get1(999): expected ErrNotFound, got: %v", err) - } -} - -func Test_FruitConf_Get2(t *testing.T) { - h := prepareHub(t) - conf := h.GetFruitConf() - - // found: APPLE -> item 1001 - item, err := conf.Get2(fruitTypeApple, 1001) - if err != nil { - t.Fatalf("Get2(%d, 1001) unexpected error: %v", fruitTypeApple, err) - } - if item.GetId() != 1001 { - t.Errorf("Get2: expected id=1001, got %d", item.GetId()) - } - if item.GetPrice() != 10 { - t.Errorf("Get2: expected price=10, got %d", item.GetPrice()) - } - - // not found: wrong item id - _, err = conf.Get2(fruitTypeApple, 9999) - if err == nil { - t.Fatal("Get2(apple, 9999): expected ErrNotFound, got nil") - } - if !errors.Is(err, loader.ErrNotFound) { - t.Fatalf("Get2(apple, 9999): expected ErrNotFound, got: %v", err) - } - - // not found: wrong fruitType - _, err = conf.Get2(999, 1001) - if !errors.Is(err, loader.ErrNotFound) { - t.Fatalf("Get2(999, 1001): expected ErrNotFound, got: %v", err) - } -} +// ---- FruitConf: leveled index (Price) finders ---- func Test_FruitConf_FindItem(t *testing.T) { h := prepareHub(t) @@ -189,6 +134,8 @@ func Test_FruitConf_FindItemMap1(t *testing.T) { } } +// ---- FruitConf: ordered index (Price@OrderedFruit) finders ---- + func Test_FruitConf_FindOrderedFruit(t *testing.T) { h := prepareHub(t) conf := h.GetFruitConf() @@ -262,7 +209,7 @@ func Test_FruitConf_FindOrderedFruit1(t *testing.T) { t.Errorf("FindOrderedFruit1(orange, 25): expected id=2002, got %d", items[0].GetId()) } - // ORANGE(2) -> price=15 -> items [2001, 2002], verify IDs are in ascending order + // ORANGE(2) -> price=15 -> items, verify IDs are in ascending order items = conf.FindOrderedFruit1(fruitTypeOrange, 15) if len(items) != 1 { t.Fatalf("FindOrderedFruit1(orange, 15): expected 1 item, got %d", len(items)) @@ -301,27 +248,7 @@ func Test_FruitConf_FindFirstOrderedFruit1(t *testing.T) { } } -// ---- Fruit6Conf ---- - -func Test_Fruit6Conf_Get1(t *testing.T) { - h := prepareHub(t) - conf := h.GetFruit6Conf() - - // found - fruit, err := conf.Get1(fruitTypeApple) - if err != nil { - t.Fatalf("Get1(%d) unexpected error: %v", fruitTypeApple, err) - } - if fruit == nil { - t.Fatal("Get1: returned nil fruit") - } - - // not found - _, err = conf.Get1(999) - if !errors.Is(err, loader.ErrNotFound) { - t.Fatalf("Get1(999): expected ErrNotFound, got: %v", err) - } -} +// ---- Fruit6Conf (Go-specific extra) ---- func Test_Fruit6Conf_FindItem(t *testing.T) { h := prepareHub(t) @@ -381,10 +308,10 @@ func Test_Fruit6Conf_FindItem1(t *testing.T) { // ORANGE(2) -> price=15 -> item 2001 items := conf.FindItem1(fruitTypeOrange, 15) if len(items) != 3 { - t.Fatalf("FindItem1(orange, 15): expected 1 item, got %d", len(items)) + t.Fatalf("FindItem1(orange, 15): expected 3 items, got %d", len(items)) } if items[0].GetId() != 2000 { - t.Errorf("FindItem1(orange, 15): expected id=2001, got %d", items[0].GetId()) + t.Errorf("FindItem1(orange, 15): expected id=2000, got %d", items[0].GetId()) } // wrong fruitType @@ -505,10 +432,10 @@ func Test_Fruit6Conf_FindOrderedFruit1(t *testing.T) { t.Errorf("FindOrderedFruit1(orange, 25): expected id=2002, got %d", items[0].GetId()) } - // ORANGE(2) -> price=15 -> items [2001, 2002], verify IDs are in ascending order + // ORANGE(2) -> price=15 -> items, verify IDs are in ascending order items = conf.FindOrderedFruit1(fruitTypeOrange, 15) if len(items) != 3 { - t.Fatalf("FindOrderedFruit1(orange, 15): expected 2 items, got %d", len(items)) + t.Fatalf("FindOrderedFruit1(orange, 15): expected 3 items, got %d", len(items)) } for i := 1; i < len(items); i++ { if items[i].GetId() < items[i-1].GetId() { @@ -541,3 +468,101 @@ func Test_Fruit6Conf_FindFirstOrderedFruit1(t *testing.T) { t.Errorf("FindFirstOrderedFruit1(banana, 999): expected nil, got %v", item) } } + +// ---- ItemConf: single-column index ---- + +func Test_ItemConf_FindItemInfoMap(t *testing.T) { + h := prepareHub(t) + conf := h.GetItemConf() + if conf == nil { + t.Fatal("ItemConf is nil") + } + if len(conf.FindItemInfoMap()) == 0 { + t.Fatal("FindItemInfoMap: expected non-empty map") + } +} + +// ---- ItemConf: multi-column (composite-key) indexes ---- +// Mirrors smoke.ts cases 15 (ParamExtType ordered index) and 16 (AwardItem index). + +// Test_ItemConf_FindParamExtType covers the ordered multi-column index +// (Param,ExtType)@ParamExtType: a sorted map whose composite keys round-trip +// through the point-lookup finder. +func Test_ItemConf_FindParamExtType(t *testing.T) { + h := prepareHub(t) + conf := h.GetItemConf() + if conf == nil { + t.Fatal("ItemConf is nil") + } + + // apple (id=1) has param_list [1,2,3] x extTypeList [APPLE, ORANGE] -> 6 buckets, + // and it is the only item carrying both columns, so the map has exactly 6 entries. + m := conf.FindParamExtTypeMap() + if m == nil { + t.Fatal("FindParamExtTypeMap: returned nil") + } + if m.Size() != 6 { + t.Errorf("FindParamExtTypeMap: expected size=6, got %d", m.Size()) + } + + // Keys are sorted ascending by (param, extType); each key round-trips through + // the point-lookup finder and agrees with the map's stored value list. + prevParam := int32(-1) + m.Range(func(key loader.ItemConf_OrderedIndex_ParamExtTypeKey, values []*protoconf.ItemConf_Item) bool { + if key.Param < prevParam { + t.Errorf("FindParamExtTypeMap: keys not ascending by param: %d after %d", key.Param, prevParam) + } + prevParam = key.Param + if got := conf.FindParamExtType(key.Param, key.ExtType); len(got) != len(values) { + t.Errorf("FindParamExtType(%d, %v): point-lookup (%d) disagrees with map iteration (%d)", + key.Param, key.ExtType, len(got), len(values)) + } + return true + }) + + // A concrete known bucket: (param=1, extType=APPLE) -> apple. + items := conf.FindParamExtType(1, protoconf.FruitType_FRUIT_TYPE_APPLE) + if len(items) != 1 { + t.Fatalf("FindParamExtType(1, APPLE): expected 1 item, got %d", len(items)) + } + if items[0].GetName() != "apple" { + t.Errorf("FindParamExtType(1, APPLE): expected name=apple, got %s", items[0].GetName()) + } + + // Missing key -> empty slice. + if items := conf.FindParamExtType(999, protoconf.FruitType_FRUIT_TYPE_APPLE); len(items) != 0 { + t.Errorf("FindParamExtType(999, APPLE): expected 0 items, got %d", len(items)) + } +} + +// Test_ItemConf_FindAwardItem covers the non-ordered multi-column index +// (ID,Name)@AwardItem. +func Test_ItemConf_FindAwardItem(t *testing.T) { + h := prepareHub(t) + conf := h.GetItemConf() + if conf == nil { + t.Fatal("ItemConf is nil") + } + + m := conf.FindAwardItemMap() + if m == nil { + t.Fatal("FindAwardItemMap: returned nil") + } + + // Every map entry round-trips through the point-lookup finder. + for key, values := range m { + if got := conf.FindAwardItem(key.Id, key.Name); len(got) != len(values) { + t.Errorf("FindAwardItem(%d, %q): point-lookup (%d) disagrees with map iteration (%d)", + key.Id, key.Name, len(got), len(values)) + } + } + + // apple is keyed by (id=1, name="apple"). + items := conf.FindAwardItem(1, "apple") + if len(items) != 1 { + t.Fatalf("FindAwardItem(1, apple): expected 1 item, got %d", len(items)) + } + if items[0].GetId() != 1 { + t.Errorf("FindAwardItem(1, apple): expected id=1, got %d", items[0].GetId()) + } +} diff --git a/test/go-tableau-loader/main_test.go b/test/go-tableau-loader/load_test.go similarity index 82% rename from test/go-tableau-loader/main_test.go rename to test/go-tableau-loader/load_test.go index b081b37..ab852a1 100644 --- a/test/go-tableau-loader/main_test.go +++ b/test/go-tableau-loader/load_test.go @@ -2,7 +2,6 @@ package loader_test import ( "context" - "errors" "testing" "github.com/tableauio/loader/test/go-tableau-loader/hub" @@ -13,6 +12,8 @@ import ( "google.golang.org/protobuf/proto" ) +// prepareHub builds and loads a MyHub from testdata. It is the shared fixture +// used by every test block (load / get / orderedmap / index) in this package. func prepareHub(t *testing.T) *hub.MyHub { t.Helper() h := hub.NewMyHub() @@ -30,6 +31,8 @@ func prepareHub(t *testing.T) *hub.MyHub { return h } +// ---- Load ---- + func Test_Load(t *testing.T) { h := prepareHub(t) for name, msger := range h.GetMessagerMap() { @@ -37,21 +40,22 @@ func Test_Load(t *testing.T) { } } -func Test_ActivityConf_NotFound(t *testing.T) { +// ---- CustomConf ---- + +func Test_CustomItemConf(t *testing.T) { h := prepareHub(t) - conf := h.GetActivityConf() - if conf == nil { - t.Fatal("ActivityConf is nil") - } - _, err := conf.Get3(100001, 1, 999) - if err == nil { - t.Fatal("expected ErrNotFound, got nil") + customConf := h.GetCustomItemConf() + if customConf == nil { + t.Fatal("CustomItemConf is nil") } - if !errors.Is(err, loader.ErrNotFound) { - t.Fatalf("expected ErrNotFound, got: %v", err) + if customConf.GetSpecialItemName() == "" { + t.Fatal("CustomItemConf: special item name is empty") } + t.Logf("specialItemName: %v", customConf.GetSpecialItemName()) } +// ---- Store ---- + func Test_ActivityConf_UpdateAndStore(t *testing.T) { h := prepareHub(t) conf := h.GetActivityConf() @@ -71,43 +75,7 @@ func Test_ActivityConf_UpdateAndStore(t *testing.T) { } } -func Test_ActivityConf_OrderedMap(t *testing.T) { - h := prepareHub(t) - conf := h.GetActivityConf() - if conf == nil { - t.Fatal("ActivityConf is nil") - } - orderedMap := conf.GetOrderedMap() - for iter := orderedMap.Iterator(); iter.Next(); { - key := iter.Key() - value := iter.Value().Second - t.Logf("key: %v, value: %v", key, value) - subOrderedMap := iter.Value().First - for iter2 := subOrderedMap.Iterator(); iter2.Next(); { - key2 := iter2.Key() - value2 := iter2.Value().Second - t.Logf(" key2: %v, value2: %v", key2, value2) - } - } -} - -func Test_CustomItemConf(t *testing.T) { - h := prepareHub(t) - customConf := h.GetCustomItemConf() - if customConf == nil { - t.Fatal("CustomItemConf is nil") - } - t.Logf("specialItemName: %v", customConf.GetSpecialItemName()) -} - -func Test_HeroBaseConf(t *testing.T) { - h := prepareHub(t) - heroConf := h.GetHeroBaseConf() - if heroConf == nil { - t.Fatal("HeroBaseConf is nil") - } - t.Logf("HeroBaseConf: %v", heroConf.Data().GetHeroMap()) -} +// ---- Context (Go-specific) ---- func Test_Context(t *testing.T) { h := prepareHub(t) @@ -130,8 +98,10 @@ func Test_Context(t *testing.T) { t.Logf("PatchReplaceConf(from background): %v", h.FromContext(context.Background()).GetPatchReplaceConf().Data()) } -// Test_Patch mirrors the patch tests in cpp-tableau-loader/src/main.cpp::TestPatch -// and csharp-tableau-loader/Program.cs::TestPatch to verify the Go patch logic. +// ---- Patch ---- +// +// Test_Patch mirrors the patch tests in cpp-tableau-loader/tests/patch_test.cpp +// and csharp-tableau-loader/tests/PatchTests.cs to verify the Go patch logic. func Test_Patch(t *testing.T) { const testdataDir = "../testdata" @@ -279,11 +249,10 @@ func Test_Patch(t *testing.T) { got := h.GetPatchMergeConf().Data() t.Logf("PatchMergeConf(OnlyPatch): %v", got) - // Without main-file content, 'name' should be the value coming from the last - // non-replace merge (still merged from patches), and replace_* fields should - // equal the last patch only. - if got.GetName() == "" { - t.Fatalf("expected non-empty Name from patches, got: %q", got.GetName()) + // Without main-file content, 'name' must come from the patches: + // patchconf sets name="orange"; patchconf2 carries no name, so it survives. + if got.GetName() != "orange" { + t.Fatalf("ModeOnlyPatch: expected Name=%q from patches, got: %q", "orange", got.GetName()) } }) } diff --git a/test/go-tableau-loader/ordered_map_test.go b/test/go-tableau-loader/ordered_map_test.go new file mode 100644 index 0000000..7e5e1f7 --- /dev/null +++ b/test/go-tableau-loader/ordered_map_test.go @@ -0,0 +1,63 @@ +package loader_test + +import ( + "testing" + + "github.com/tableauio/loader/test/go-tableau-loader/protoconf" +) + +// ---- ActivityConf: nested ordered map ---- +// getOrderedMap exposes the full sorted tree; each node carries its value +// (.Second) and the next-level sub-map (.First). + +func Test_ActivityConf_OrderedMap(t *testing.T) { + h := prepareHub(t) + conf := h.GetActivityConf() + if conf == nil { + t.Fatal("ActivityConf is nil") + } + orderedMap := conf.GetOrderedMap() + if orderedMap == nil || orderedMap.Size() == 0 { + t.Fatal("ActivityConf ordered map is empty") + } + + // 1st level: keys ascending; node.Second is the Activity value. + prev := uint64(0) + for iter := orderedMap.Iterator(); iter.Next(); { + key := iter.Key() + if key < prev { + t.Errorf("ordered map keys not ascending: %d after %d", key, prev) + } + prev = key + + subOrderedMap := iter.Value().First + for iter2 := subOrderedMap.Iterator(); iter2.Next(); { + _ = iter2.Key() + _ = iter2.Value().Second + } + } +} + +// ---- ItemConf: 1st-level ordered map ---- + +func Test_ItemConf_OrderedMap(t *testing.T) { + h := prepareHub(t) + conf := h.GetItemConf() + if conf == nil { + t.Fatal("ItemConf is nil") + } + orderedMap := conf.GetOrderedMap() + if orderedMap == nil || orderedMap.Size() == 0 { + t.Fatal("ItemConf ordered map is empty") + } + + // keys ascending by id; prev starts at 0 so iteration 1 (key >= 0) holds. + prev := uint32(0) + orderedMap.Range(func(key uint32, _ *protoconf.ItemConf_Item) bool { + if key < prev { + t.Errorf("ItemConf ordered map keys not ascending: %d after %d", key, prev) + } + prev = key + return true + }) +} diff --git a/test/go-tableau-loader/protoconf/loader/hero_conf.pc.go b/test/go-tableau-loader/protoconf/loader/hero_conf.pc.go index 85d23cc..4798904 100644 --- a/test/go-tableau-loader/protoconf/loader/hero_conf.pc.go +++ b/test/go-tableau-loader/protoconf/loader/hero_conf.pc.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-go-tableau-loader v0.11.0 +// - protoc-gen-go-tableau-loader v0.12.0 // - protoc (unknown) // source: hero_conf.proto diff --git a/test/go-tableau-loader/protoconf/loader/hub.pc.go b/test/go-tableau-loader/protoconf/loader/hub.pc.go index 435279d..7419bfa 100644 --- a/test/go-tableau-loader/protoconf/loader/hub.pc.go +++ b/test/go-tableau-loader/protoconf/loader/hub.pc.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-go-tableau-loader v0.11.0 +// - protoc-gen-go-tableau-loader v0.12.0 // - protoc (unknown) package loader diff --git a/test/go-tableau-loader/protoconf/loader/index_conf.pc.go b/test/go-tableau-loader/protoconf/loader/index_conf.pc.go index 379b0c4..5498f1e 100644 --- a/test/go-tableau-loader/protoconf/loader/index_conf.pc.go +++ b/test/go-tableau-loader/protoconf/loader/index_conf.pc.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-go-tableau-loader v0.11.0 +// - protoc-gen-go-tableau-loader v0.12.0 // - protoc (unknown) // source: index_conf.proto diff --git a/test/go-tableau-loader/protoconf/loader/item_conf.pc.go b/test/go-tableau-loader/protoconf/loader/item_conf.pc.go index 563b8cd..b7405ca 100644 --- a/test/go-tableau-loader/protoconf/loader/item_conf.pc.go +++ b/test/go-tableau-loader/protoconf/loader/item_conf.pc.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-go-tableau-loader v0.11.0 +// - protoc-gen-go-tableau-loader v0.12.0 // - protoc (unknown) // source: item_conf.proto diff --git a/test/go-tableau-loader/protoconf/loader/messager.pc.go b/test/go-tableau-loader/protoconf/loader/messager.pc.go index 60c5b98..47aa5bf 100644 --- a/test/go-tableau-loader/protoconf/loader/messager.pc.go +++ b/test/go-tableau-loader/protoconf/loader/messager.pc.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-go-tableau-loader v0.11.0 +// - protoc-gen-go-tableau-loader v0.12.0 // - protoc (unknown) package loader diff --git a/test/go-tableau-loader/protoconf/loader/messager_container.pc.go b/test/go-tableau-loader/protoconf/loader/messager_container.pc.go index 96b08f3..91e26d0 100644 --- a/test/go-tableau-loader/protoconf/loader/messager_container.pc.go +++ b/test/go-tableau-loader/protoconf/loader/messager_container.pc.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-go-tableau-loader v0.11.0 +// - protoc-gen-go-tableau-loader v0.12.0 // - protoc (unknown) package loader diff --git a/test/go-tableau-loader/protoconf/loader/patch_conf.pc.go b/test/go-tableau-loader/protoconf/loader/patch_conf.pc.go index 31c8845..2e18b1c 100644 --- a/test/go-tableau-loader/protoconf/loader/patch_conf.pc.go +++ b/test/go-tableau-loader/protoconf/loader/patch_conf.pc.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-go-tableau-loader v0.11.0 +// - protoc-gen-go-tableau-loader v0.12.0 // - protoc (unknown) // source: patch_conf.proto diff --git a/test/go-tableau-loader/protoconf/loader/test_conf.pc.go b/test/go-tableau-loader/protoconf/loader/test_conf.pc.go index 466eb20..b76c852 100644 --- a/test/go-tableau-loader/protoconf/loader/test_conf.pc.go +++ b/test/go-tableau-loader/protoconf/loader/test_conf.pc.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-go-tableau-loader v0.11.0 +// - protoc-gen-go-tableau-loader v0.12.0 // - protoc (unknown) // source: test_conf.proto diff --git a/test/go-tableau-loader/protoconf/loader/util.pc.go b/test/go-tableau-loader/protoconf/loader/util.pc.go index 11ea5d9..183997c 100644 --- a/test/go-tableau-loader/protoconf/loader/util.pc.go +++ b/test/go-tableau-loader/protoconf/loader/util.pc.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go-tableau-loader. DO NOT EDIT. // versions: -// - protoc-gen-go-tableau-loader v0.11.0 +// - protoc-gen-go-tableau-loader v0.12.0 // - protoc (unknown) package loader