Skip to content

Commit 0be9d31

Browse files
committed
Тесты: запись формата 8.3.8 и пропуск BOM при разборе дерева
Добавлены проверки: рост числа блоков объекта при записи (set_data), установка версии объекта без затирания длины (set_version), пометка записи удалённой и пропуск UTF-8 BOM в начале потока. Тесты записи работают на копии эталонной базы tests/db838/db01.
1 parent 70556ab commit 0be9d31

2 files changed

Lines changed: 268 additions & 0 deletions

File tree

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
test_project provides tests for Tool1CD library
3+
Copyright © 2009-2017 awa
4+
Copyright © 2017-2018 E8 Tools contributors
5+
6+
This file is part of test_project.
7+
8+
test_project is free software: you can redistribute it and/or modify
9+
it under the terms of the GNU General Public License as published by
10+
the Free Software Foundation, either version 3 of the License, or
11+
(at your option) any later version.
12+
13+
test_project is distributed in the hope that it will be useful,
14+
but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
GNU General Public License for more details.
17+
18+
You should have received a copy of the GNU General Public License
19+
along with test_project. If not, see <http://www.gnu.org/licenses/>.
20+
*/
21+
#include "../catch.hpp"
22+
#include <Parse_tree.h>
23+
#include <string>
24+
#include <utility>
25+
#include <SystemClasses/TMemoryStream.hpp>
26+
#include <SystemClasses/System.SysUtils.hpp>
27+
28+
// Прерванное динамическое обновление оставляет в служебных файлах конфигурации
29+
// (например, DynamicallyUpdated) метку порядка байтов UTF-8. Разбор такого
30+
// потока не должен прерываться — BOM пропускается.
31+
TEST_CASE( "Parse_tree пропускает UTF-8 BOM в начале потока", "[tool1cd][common][Parse_tree][BOM]" ) {
32+
33+
std::string tree_source = "{1,2,{3,4}\n}\n";
34+
35+
GIVEN ("Поток дерева с UTF-8 BOM (EF BB BF) в начале") {
36+
37+
auto src = TEncoding::UTF8->fromUtf8(tree_source);
38+
decltype(src) with_bom;
39+
with_bom.push_back(0xEF);
40+
with_bom.push_back(0xBB);
41+
with_bom.push_back(0xBF);
42+
with_bom.insert(with_bom.end(), src.begin(), src.end());
43+
44+
WHEN ("Разбираем поток") {
45+
46+
TBytesStream bs(std::move(with_bom));
47+
auto tree = parse_1Cstream(&bs, "");
48+
49+
THEN ("Дерево разбирается так же, как без BOM") {
50+
51+
REQUIRE(tree != nullptr);
52+
std::string out_with_bom;
53+
tree->outtext(out_with_bom);
54+
55+
auto reference = parse_1Ctext(tree_source, "");
56+
REQUIRE(reference != nullptr);
57+
std::string out_reference;
58+
reference->outtext(out_reference);
59+
60+
REQUIRE(out_with_bom == out_reference);
61+
}
62+
}
63+
}
64+
}
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
/*
2+
test_project provides tests for Tool1CD library
3+
Copyright © 2009-2017 awa
4+
Copyright © 2017-2018 E8 Tools contributors
5+
6+
This file is part of test_project.
7+
8+
test_project is free software: you can redistribute it and/or modify
9+
it under the terms of the GNU General Public License as published by
10+
the Free Software Foundation, either version 3 of the License, or
11+
(at your option) any later version.
12+
13+
test_project is distributed in the hope that it will be useful,
14+
but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
GNU General Public License for more details.
17+
18+
You should have received a copy of the GNU General Public License
19+
along with test_project. If not, see <http://www.gnu.org/licenses/>.
20+
*/
21+
#include "../catch.hpp"
22+
#include <Class_1CD.h>
23+
#include <Table.h>
24+
#include <TableRecord.h>
25+
#include <V8Object.h>
26+
#include <boost/filesystem.hpp>
27+
#include <cstring>
28+
#include <cstdint>
29+
#include <string>
30+
#include <vector>
31+
32+
using boost::filesystem::path;
33+
34+
// Копирует эталонную базу из дерева исходников во временный файл, чтобы тесты
35+
// записи не меняли фикстуру в репозитории.
36+
static path copy_fixture(const std::string &relpath)
37+
{
38+
path src = path(CMAKE_SOURCE_DIR) / relpath;
39+
path dst = boost::filesystem::temp_directory_path()
40+
/ boost::filesystem::unique_path("tool1cd_%%%%%%%%.1CD");
41+
boost::filesystem::copy_file(src, dst);
42+
return dst;
43+
}
44+
45+
// Первая таблица, у которой есть объект данных.
46+
static Table *first_table_with_data(T_1CD &base)
47+
{
48+
for (int i = 0; i < base.get_numtables(); i++) {
49+
if (base.get_table(i)->get_file_data() != nullptr) {
50+
return base.get_table(i);
51+
}
52+
}
53+
return nullptr;
54+
}
55+
56+
static Table *table_by_name(T_1CD &base, const std::string &name)
57+
{
58+
for (int i = 0; i < base.get_numtables(); i++) {
59+
if (base.get_table(i)->get_name() == name) {
60+
return base.get_table(i);
61+
}
62+
}
63+
return nullptr;
64+
}
65+
66+
// Запись объекта в формате 8.3.8 с ростом числа блоков (fatlevel 0).
67+
// Раньше при росте не обновлялось число блоков объекта и запись уходила
68+
// в нулевой блок (заголовок базы) — здесь это проверяется явно.
69+
TEST_CASE("set_data растит объект формата 8.3.8 и читает данные обратно",
70+
"[tool1cd][V8Object][write][8.3.8]")
71+
{
72+
path dbfile = copy_fixture("tests/db838/db01/1Cv8.1CD");
73+
74+
{
75+
T_1CD base(dbfile, nullptr, true);
76+
REQUIRE(base.is_open());
77+
78+
Table *t = first_table_with_data(base);
79+
REQUIRE(t != nullptr);
80+
81+
V8Object *fd = t->get_file_data();
82+
uint64_t old_len = fd->get_len();
83+
84+
// Гарантированно несколько блоков (fatlevel 0): +30 страниц по 8192 байта.
85+
uint64_t new_len = old_len + 30ull * 8192ull;
86+
std::vector<char> pattern(new_len);
87+
for (uint64_t i = 0; i < new_len; i++) {
88+
pattern[i] = static_cast<char>((i * 131 + 17) & 0xFF);
89+
}
90+
91+
WHEN("Записываем объект целиком большего размера") {
92+
// Если число блоков не обновлено, запись попадёт в блок 0 и сработает
93+
// защита get_block_for_write — set_data бросит исключение.
94+
REQUIRE(fd->set_data(pattern.data(), new_len));
95+
96+
THEN("Длина объекта и его данные соответствуют записанным") {
97+
REQUIRE(fd->get_len() == new_len);
98+
99+
std::vector<char> readback(new_len);
100+
fd->get_data(readback.data(), 0, new_len);
101+
REQUIRE(std::memcmp(readback.data(), pattern.data(), new_len) == 0);
102+
}
103+
}
104+
}
105+
106+
boost::filesystem::remove(dbfile);
107+
}
108+
109+
// set_version должна писать версию по смещению, верному для формата 8.3.8,
110+
// и не затрагивать длину объекта (раньше версия писалась поверх длины).
111+
TEST_CASE("set_version пишет версию и не затирает длину объекта (8.3.8)",
112+
"[tool1cd][V8Object][write][8.3.8]")
113+
{
114+
path dbfile = copy_fixture("tests/db838/db01/1Cv8.1CD");
115+
116+
std::string table_name;
117+
uint64_t original_len = 0;
118+
119+
{
120+
T_1CD base(dbfile, nullptr, true);
121+
REQUIRE(base.is_open());
122+
123+
Table *t = first_table_with_data(base);
124+
REQUIRE(t != nullptr);
125+
table_name = t->get_name();
126+
127+
V8Object *fd = t->get_file_data();
128+
original_len = fd->get_len();
129+
130+
fd->set_version(0x01020304, 0x05060708);
131+
base.flush();
132+
}
133+
134+
GIVEN("Переоткрытая после записи версии база") {
135+
T_1CD reopened(dbfile, nullptr, true);
136+
REQUIRE(reopened.is_open());
137+
138+
Table *t = table_by_name(reopened, table_name);
139+
REQUIRE(t != nullptr);
140+
V8Object *fd = t->get_file_data();
141+
142+
THEN("Длина объекта сохранилась, а версия записана корректно") {
143+
REQUIRE(fd->get_len() == original_len);
144+
145+
auto ver = fd->get_current_version();
146+
REQUIRE(ver.version_1 == 0x01020304);
147+
REQUIRE(ver.version_2 == 0x05060708);
148+
}
149+
}
150+
151+
boost::filesystem::remove(dbfile);
152+
}
153+
154+
// Пометка записи удалённой должна сохраняться при переоткрытии базы.
155+
TEST_CASE("mark_record_removed помечает запись удалённой (8.3.8)",
156+
"[tool1cd][Table][write][8.3.8]")
157+
{
158+
path dbfile = copy_fixture("tests/db838/db01/1Cv8.1CD");
159+
160+
std::string table_name;
161+
uint32_t record_no = 0;
162+
163+
{
164+
T_1CD base(dbfile, nullptr, true);
165+
REQUIRE(base.is_open());
166+
167+
// Ищем первую неудалённую запись в любой таблице.
168+
for (int i = 0; i < base.get_numtables() && record_no == 0; i++) {
169+
Table *t = base.get_table(i);
170+
uint32_t total = t->get_phys_numrecords();
171+
for (uint32_t r = 1; r < total; r++) {
172+
TableRecord *rec = t->get_record(r);
173+
bool removed = rec->is_removed();
174+
delete rec;
175+
if (!removed) {
176+
table_name = t->get_name();
177+
record_no = r;
178+
t->begin_edit();
179+
t->mark_record_removed(r);
180+
break;
181+
}
182+
}
183+
}
184+
REQUIRE(record_no != 0);
185+
base.flush();
186+
}
187+
188+
GIVEN("Переоткрытая после пометки база") {
189+
T_1CD reopened(dbfile, nullptr, true);
190+
REQUIRE(reopened.is_open());
191+
192+
Table *t = table_by_name(reopened, table_name);
193+
REQUIRE(t != nullptr);
194+
195+
THEN("Запись отмечена как удалённая") {
196+
TableRecord *rec = t->get_record(record_no);
197+
bool removed = rec->is_removed();
198+
delete rec;
199+
REQUIRE(removed);
200+
}
201+
}
202+
203+
boost::filesystem::remove(dbfile);
204+
}

0 commit comments

Comments
 (0)