File: evolution_test.cpp

package info (click to toggle)
golang-github-google-flatbuffers 24.12.23-1
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 17,704 kB
  • sloc: cpp: 53,217; python: 6,900; cs: 5,566; java: 4,370; php: 1,460; javascript: 1,061; xml: 1,016; sh: 886; makefile: 13
file content (190 lines) | stat: -rw-r--r-- 8,177 bytes parent folder | download | duplicates (10)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#include "evolution_test.h"

#include "evolution_test/evolution_v1_generated.h"
#include "evolution_test/evolution_v2_generated.h"
#include "flatbuffers/idl.h"
#include "test_assert.h"

namespace flatbuffers {
namespace tests {

void EvolutionTest(const std::string &tests_data_path) {
  // VS10 does not support typed enums, exclude from tests
#if !defined(_MSC_VER) || _MSC_VER >= 1700
  const int NUM_VERSIONS = 2;
  std::string schemas[NUM_VERSIONS];
  std::string jsonfiles[NUM_VERSIONS];
  std::vector<uint8_t> binaries[NUM_VERSIONS];

  flatbuffers::IDLOptions idl_opts;
  idl_opts.lang_to_generate |= flatbuffers::IDLOptions::kBinary;
  flatbuffers::Parser parser(idl_opts);

  // Load all the schema versions and their associated data.
  for (int i = 0; i < NUM_VERSIONS; ++i) {
    std::string schema = tests_data_path + "evolution_test/evolution_v" +
                         flatbuffers::NumToString(i + 1) + ".fbs";
    TEST_ASSERT(flatbuffers::LoadFile(schema.c_str(), false, &schemas[i]));
    std::string json = tests_data_path + "evolution_test/evolution_v" +
                       flatbuffers::NumToString(i + 1) + ".json";
    TEST_ASSERT(flatbuffers::LoadFile(json.c_str(), false, &jsonfiles[i]));

    TEST_ASSERT(parser.Parse(schemas[i].c_str()));
    TEST_ASSERT(parser.Parse(jsonfiles[i].c_str()));

    auto bufLen = parser.builder_.GetSize();
    auto buf = parser.builder_.GetBufferPointer();
    binaries[i].reserve(bufLen);
    std::copy(buf, buf + bufLen, std::back_inserter(binaries[i]));
  }

  // Assert that all the verifiers for the different schema versions properly
  // verify any version data.
  for (int i = 0; i < NUM_VERSIONS; ++i) {
    flatbuffers::Verifier verifier(&binaries[i].front(), binaries[i].size());
    TEST_ASSERT(Evolution::V1::VerifyRootBuffer(verifier));
    TEST_ASSERT(Evolution::V2::VerifyRootBuffer(verifier));
  }

  // Test backwards compatibility by reading old data with an evolved schema.
  auto root_v1_viewed_from_v2 = Evolution::V2::GetRoot(&binaries[0].front());
  // field 'k' is new in version 2, so it should be null.
  TEST_ASSERT(nullptr == root_v1_viewed_from_v2->k());
  // field 'l' is new in version 2 with a default of 56.
  TEST_EQ(root_v1_viewed_from_v2->l(), 56);
  // field 'c' of 'TableA' is new in version 2, so it should be null.
  TEST_ASSERT(nullptr == root_v1_viewed_from_v2->e()->c());
  // 'TableC' was added to field 'c' union in version 2, so it should be null.
  TEST_ASSERT(nullptr == root_v1_viewed_from_v2->c_as_TableC());
  // The field 'c' union should be of type 'TableB' regardless of schema version
  TEST_ASSERT(root_v1_viewed_from_v2->c_type() == Evolution::V2::Union::TableB);
  // The field 'f' was renamed to 'ff' in version 2, it should still be
  // readable.
  TEST_EQ(root_v1_viewed_from_v2->ff()->a(), 16);

  // Test forwards compatibility by reading new data with an old schema.
  auto root_v2_viewed_from_v1 = Evolution::V1::GetRoot(&binaries[1].front());
  // The field 'c' union in version 2 is a new table (index = 3) and should
  // still be accessible, but not interpretable.
  TEST_EQ(static_cast<uint8_t>(root_v2_viewed_from_v1->c_type()), 3);
  TEST_NOTNULL(root_v2_viewed_from_v1->c());
  // The field 'd' enum in verison 2 has new members and should still be
  // accessible, but not interpretable.
  TEST_EQ(static_cast<int8_t>(root_v2_viewed_from_v1->d()), 3);
  // The field 'a' in version 2 is deprecated and should return the default
  // value (0) instead of the value stored in the in the buffer (42).
  TEST_EQ(root_v2_viewed_from_v1->a(), 0);
  // The field 'ff' was originally named 'f' in version 1, it should still be
  // readable.
  TEST_EQ(root_v2_viewed_from_v1->f()->a(), 35);
#endif
}

void ConformTest() {
  const char ref[] = "table T { A:int; } enum E:byte { A }";

  auto test_conform = [](const char *ref, const char *test,
                         const char *expected_err) {
    flatbuffers::Parser parser1;
    TEST_EQ(parser1.Parse(ref), true);
    flatbuffers::Parser parser2;
    TEST_EQ(parser2.Parse(test), true);
    auto err = parser2.ConformTo(parser1);
    if (*expected_err == '\0') {
      TEST_EQ_STR(err.c_str(), expected_err);
    } else {
      TEST_NOTNULL(strstr(err.c_str(), expected_err));
    }
  };

  test_conform(ref, "table T { A:byte; }", "types differ for field: T.A");
  test_conform(ref, "table T { B:int; A:int; }",
               "offsets differ for field: T.A");
  test_conform(ref, "table T { A:int = 1; }", "defaults differ for field: T.A");
  test_conform(ref, "table T { B:float; }",
               "field renamed to different type: T.B (renamed from T.A)");
  test_conform(ref, "enum E:byte { B, A }", "values differ for enum: A");
  test_conform(ref, "table T { }", "field deleted: T.A");
  test_conform(ref, "table T { B:int; }", "");  // renaming a field is allowed

  const char ref2[] = "enum E:byte { A } table T2 { f:E; } ";
  test_conform(ref2, "enum E:int32 { A } table T2 { df:byte; f:E; }",
               "field renamed to different type: T2.df (renamed from T2.f)");

  // Check enum underlying type changes.
  test_conform("enum E:int32 {A}", "enum E: byte {A}", "underlying type differ for enum: E");
  
  // Check union underlying type changes.
  const char ref3[] = "table A {} table B {} union C {A, B}";
  test_conform(ref3, "table A {} table B {} union C:int32 {A, B}", "underlying type differ for union: C");

  // Check conformity for Offset64-related changes.
  {
    const char ref[] = "table T { a:[uint8]; b:string; }";

    // Adding a 'vector64' changes the type.
    test_conform(ref, "table T { a:[uint8] (vector64); b:string; }",
                 "types differ for field: T.a");

    // Adding a 'offset64' to the vector changes the type.
    test_conform(ref, "table T { a:[uint8] (offset64); b:string; }",
                 "offset types differ for field: T.a");

    // Adding a 'offset64' to the string also changes the type.
    test_conform(ref, "table T { a:[uint8]; b:string (offset64); }",
                 "offset types differ for field: T.b");

    // Now try the opposite direction of removing an attribute from an existing
    // field.

    // Removing a 'vector64' changes the type.
    test_conform("table T { a:[uint8] (vector64); b:string; }", ref,
                 "types differ for field: T.a");

    // Removing a 'offset64' to the string also changes the type.
    test_conform("table T { a:[uint8] (offset64); b:string; }", ref,
                 "offset types differ for field: T.a");

    // Remove a 'offset64' to the string also changes the type.
    test_conform("table T { a:[uint8]; b:string (offset64); }", ref,
                 "offset types differ for field: T.b");
  }
}

void UnionDeprecationTest(const std::string &tests_data_path) {
  const int NUM_VERSIONS = 2;
  std::string schemas[NUM_VERSIONS];
  std::string jsonfiles[NUM_VERSIONS];
  std::vector<uint8_t> binaries[NUM_VERSIONS];

  flatbuffers::IDLOptions idl_opts;
  idl_opts.lang_to_generate |= flatbuffers::IDLOptions::kBinary;
  flatbuffers::Parser parser(idl_opts);

  // Load all the schema versions and their associated data.
  for (int i = 0; i < NUM_VERSIONS; ++i) {
    std::string schema = tests_data_path + "evolution_test/evolution_v" +
                         flatbuffers::NumToString(i + 1) + ".fbs";
    TEST_ASSERT(flatbuffers::LoadFile(schema.c_str(), false, &schemas[i]));
    std::string json = tests_data_path + "evolution_test/evolution_v" +
                       flatbuffers::NumToString(i + 1) + ".json";
    TEST_ASSERT(flatbuffers::LoadFile(json.c_str(), false, &jsonfiles[i]));

    TEST_ASSERT(parser.Parse(schemas[i].c_str()));
    TEST_ASSERT(parser.Parse(jsonfiles[i].c_str()));

    auto bufLen = parser.builder_.GetSize();
    auto buf = parser.builder_.GetBufferPointer();
    binaries[i].reserve(bufLen);
    std::copy(buf, buf + bufLen, std::back_inserter(binaries[i]));
  }

  auto v2 = parser.LookupStruct("Evolution.V2.Root");
  TEST_NOTNULL(v2);
  auto j_type_field = v2->fields.Lookup("j_type");
  TEST_NOTNULL(j_type_field);
  TEST_ASSERT(j_type_field->deprecated);
}

}  // namespace tests
}  // namespace flatbuffers