diff --git a/CMakeLists.txt b/CMakeLists.txt index 93dac0aaf98..f1485cff3e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -688,6 +688,8 @@ if(FLATBUFFERS_BUILD_TESTS) enable_testing() add_test(NAME flattests COMMAND flattests) + # Add CMake tests defined in the `tests` directory (python/unit tests). + add_subdirectory(tests) if(FLATBUFFERS_BUILD_CPP17) add_test(NAME flattests_cpp17 COMMAND flattests_cpp17) endif() diff --git a/goldens/lua/MyGame/Example/NamespaceTest.lua b/goldens/lua/MyGame/Example/NamespaceTest.lua new file mode 100644 index 00000000000..29192a1f809 --- /dev/null +++ b/goldens/lua/MyGame/Example/NamespaceTest.lua @@ -0,0 +1,70 @@ +--[[ MyGame.Example.NamespaceTest + + Automatically generated by the FlatBuffers compiler, do not modify. + Or modify. I'm a message, not a cop. + + flatc version: 25.9.23 + + Declared by : //namespace_first.fbs + Rooting type : MyGame.Example.NamespaceTest (//namespace_first.fbs) + +--]] + +local flatbuffers = require('flatbuffers') + +local NamespaceTest = {} +local mt = {} + +function NamespaceTest.New() + local o = {} + setmetatable(o, {__index = mt}) + return o +end + +function NamespaceTest.GetRootAsNamespaceTest(buf, offset) + if type(buf) == "string" then + buf = flatbuffers.binaryArray.New(buf) + end + + local n = flatbuffers.N.UOffsetT:Unpack(buf, offset) + local o = NamespaceTest.New() + o:Init(buf, n + offset) + return o +end + +function mt:Init(buf, pos) + self.view = flatbuffers.view.New(buf, pos) +end + +function mt:Name() + local o = self.view:Offset(4) + if o ~= 0 then + return self.view:String(self.view.pos + o) + end +end + +function mt:Value() + local o = self.view:Offset(6) + if o ~= 0 then + return self.view:Get(flatbuffers.N.Int32, self.view.pos + o) + end + return 0 +end + +function NamespaceTest.Start(builder) + builder:StartObject(2) +end + +function NamespaceTest.AddName(builder, name) + builder:PrependUOffsetTRelativeSlot(0, name, 0) +end + +function NamespaceTest.AddValue(builder, value) + builder:PrependInt32Slot(1, value, 0) +end + +function NamespaceTest.End(builder) + return builder:EndObject() +end + +return NamespaceTest \ No newline at end of file diff --git a/goldens/lua/flatbuffers/goldens/Galaxy.lua b/goldens/lua/flatbuffers/goldens/Galaxy.lua new file mode 100644 index 00000000000..47e606919fa --- /dev/null +++ b/goldens/lua/flatbuffers/goldens/Galaxy.lua @@ -0,0 +1,48 @@ +--[[ flatbuffers.goldens.Galaxy + + Automatically generated by the FlatBuffers compiler, do not modify. + Or modify. I'm a message, not a cop. + + flatc version: 25.9.23 + + Declared by : //basic.fbs + Rooting type : flatbuffers.goldens.Universe (//basic.fbs) + +--]] + +local flatbuffers = require('flatbuffers') + +local Galaxy = {} +local mt = {} + +function Galaxy.New() + local o = {} + setmetatable(o, {__index = mt}) + return o +end + +function mt:Init(buf, pos) + self.view = flatbuffers.view.New(buf, pos) +end + +function mt:NumStars() + local o = self.view:Offset(4) + if o ~= 0 then + return self.view:Get(flatbuffers.N.Int64, self.view.pos + o) + end + return 0 +end + +function Galaxy.Start(builder) + builder:StartObject(1) +end + +function Galaxy.AddNumStars(builder, numStars) + builder:PrependInt64Slot(0, numStars, 0) +end + +function Galaxy.End(builder) + return builder:EndObject() +end + +return Galaxy \ No newline at end of file diff --git a/goldens/lua/flatbuffers/goldens/Universe.lua b/goldens/lua/flatbuffers/goldens/Universe.lua new file mode 100644 index 00000000000..78400a8e9c0 --- /dev/null +++ b/goldens/lua/flatbuffers/goldens/Universe.lua @@ -0,0 +1,88 @@ +--[[ flatbuffers.goldens.Universe + + Automatically generated by the FlatBuffers compiler, do not modify. + Or modify. I'm a message, not a cop. + + flatc version: 25.9.23 + + Declared by : //basic.fbs + Rooting type : flatbuffers.goldens.Universe (//basic.fbs) + +--]] + +local __flatbuffers_goldens_Galaxy = require('flatbuffers.goldens.Galaxy') +local flatbuffers = require('flatbuffers') + +local Universe = {} +local mt = {} + +function Universe.New() + local o = {} + setmetatable(o, {__index = mt}) + return o +end + +function Universe.GetRootAsUniverse(buf, offset) + if type(buf) == "string" then + buf = flatbuffers.binaryArray.New(buf) + end + + local n = flatbuffers.N.UOffsetT:Unpack(buf, offset) + local o = Universe.New() + o:Init(buf, n + offset) + return o +end + +function mt:Init(buf, pos) + self.view = flatbuffers.view.New(buf, pos) +end + +function mt:Age() + local o = self.view:Offset(4) + if o ~= 0 then + return self.view:Get(flatbuffers.N.Float64, self.view.pos + o) + end + return 0.0 +end + +function mt:Galaxies(j) + local o = self.view:Offset(6) + if o ~= 0 then + local x = self.view:Vector(o) + x = x + ((j-1) * 4) + x = self.view:Indirect(x) + local obj = __flatbuffers_goldens_Galaxy.New() + obj:Init(self.view.bytes, x) + return obj + end +end + +function mt:GalaxiesLength() + local o = self.view:Offset(6) + if o ~= 0 then + return self.view:VectorLen(o) + end + return 0 +end + +function Universe.Start(builder) + builder:StartObject(2) +end + +function Universe.AddAge(builder, age) + builder:PrependFloat64Slot(0, age, 0.0) +end + +function Universe.AddGalaxies(builder, galaxies) + builder:PrependUOffsetTRelativeSlot(1, galaxies, 0) +end + +function Universe.StartGalaxiesVector(builder, numElems) + return builder:StartVector(4, numElems, 4) +end + +function Universe.End(builder) + return builder:EndObject() +end + +return Universe \ No newline at end of file diff --git a/goldens/lua/generate.py b/goldens/lua/generate.py index 108a4d580ed..1ecbd2fec6a 100644 --- a/goldens/lua/generate.py +++ b/goldens/lua/generate.py @@ -8,3 +8,5 @@ def flatc(options, schema): def GenerateLua(): flatc([], "basic.fbs") + # Test schema that starts with namespace declaration + flatc([], "namespace_first.fbs") diff --git a/goldens/nim/flatbuffers/goldens/Galaxy.nim b/goldens/nim/flatbuffers/goldens/Galaxy.nim new file mode 100644 index 00000000000..77172bed3d6 --- /dev/null +++ b/goldens/nim/flatbuffers/goldens/Galaxy.nim @@ -0,0 +1,26 @@ +#[ flatbuffers.goldens.Galaxy + Automatically generated by the FlatBuffers compiler, do not modify. + Or modify. I'm a message, not a cop. + + flatc version: 25.9.23 + + Declared by : //basic.fbs + Rooting type : flatbuffers.goldens.Universe (//basic.fbs) +]# + +import flatbuffers + +type Galaxy* = object of FlatObj +func numStars*(self: Galaxy): int64 = + let o = self.tab.Offset(4) + if o != 0: + return Get[int64](self.tab, self.tab.Pos + o) + return 0 +func `numStars=`*(self: var Galaxy, n: int64): bool = + return self.tab.MutateSlot(4, n) +proc GalaxyStart*(builder: var Builder) = + builder.StartObject(1) +proc GalaxyAddnumStars*(builder: var Builder, numStars: int64) = + builder.PrependSlot(0, numStars, default(int64)) +proc GalaxyEnd*(builder: var Builder): uoffset = + return builder.EndObject() diff --git a/goldens/nim/flatbuffers/goldens/Universe.nim b/goldens/nim/flatbuffers/goldens/Universe.nim new file mode 100644 index 00000000000..3bf3daa4b7d --- /dev/null +++ b/goldens/nim/flatbuffers/goldens/Universe.nim @@ -0,0 +1,46 @@ +#[ flatbuffers.goldens.Universe + Automatically generated by the FlatBuffers compiler, do not modify. + Or modify. I'm a message, not a cop. + + flatc version: 25.9.23 + + Declared by : //basic.fbs + Rooting type : flatbuffers.goldens.Universe (//basic.fbs) +]# + +import Galaxy as flatbuffers_goldens_Galaxy +import flatbuffers +import std/options + +type Universe* = object of FlatObj +func age*(self: Universe): float64 = + let o = self.tab.Offset(4) + if o != 0: + return Get[float64](self.tab, self.tab.Pos + o) + return 0.0 +func `age=`*(self: var Universe, n: float64): bool = + return self.tab.MutateSlot(4, n) +func galaxiesLength*(self: Universe): int = + let o = self.tab.Offset(6) + if o != 0: + return self.tab.VectorLen(o) +func galaxies*(self: Universe, j: int): flatbuffers_goldens_Galaxy.Galaxy = + let o = self.tab.Offset(6) + if o != 0: + var x = self.tab.Vector(o) + x += j.uoffset * 4.uoffset + return flatbuffers_goldens_Galaxy.Galaxy(tab: Vtable(Bytes: self.tab.Bytes, Pos: x)) +func galaxies*(self: Universe): seq[flatbuffers_goldens_Galaxy.Galaxy] = + let len = self.galaxiesLength + for i in countup(0, len - 1): + result.add(self.galaxies(i)) +proc UniverseStart*(builder: var Builder) = + builder.StartObject(2) +proc UniverseAddage*(builder: var Builder, age: float64) = + builder.PrependSlot(0, age, default(float64)) +proc UniverseAddgalaxies*(builder: var Builder, galaxies: uoffset) = + builder.PrependSlot(1, galaxies, default(uoffset)) +proc UniverseStartgalaxiesVector*(builder: var Builder, numElems: uoffset) = + builder.StartVector(4, numElems, 4) +proc UniverseEnd*(builder: var Builder): uoffset = + return builder.EndObject() diff --git a/goldens/schema/namespace_first.fbs b/goldens/schema/namespace_first.fbs new file mode 100644 index 00000000000..5dfe1fd5496 --- /dev/null +++ b/goldens/schema/namespace_first.fbs @@ -0,0 +1,8 @@ +namespace MyGame.Example; + +table NamespaceTest { + name:string; + value:int; +} + +root_type NamespaceTest; \ No newline at end of file diff --git a/goldens/swift/basic_generated.swift b/goldens/swift/basic_generated.swift index 852868bfe7b..4182078dd64 100644 --- a/goldens/swift/basic_generated.swift +++ b/goldens/swift/basic_generated.swift @@ -8,7 +8,7 @@ import Common import FlatBuffers -public struct flatbuffers_goldens_Galaxy: FlatBufferObject, Verifiable { +public struct flatbuffers_goldens_Galaxy: FlatBufferTable, FlatbuffersVectorInitializable, Verifiable { static func validateVersion() { FlatBuffersVersion_25_9_23() } public var __buffer: ByteBuffer! { return _accessor.bb } @@ -43,7 +43,7 @@ public struct flatbuffers_goldens_Galaxy: FlatBufferObject, Verifiable { } } -public struct flatbuffers_goldens_Universe: FlatBufferObject, Verifiable { +public struct flatbuffers_goldens_Universe: FlatBufferTable, FlatbuffersVectorInitializable, Verifiable { static func validateVersion() { FlatBuffersVersion_25_9_23() } public var __buffer: ByteBuffer! { return _accessor.bb } @@ -60,9 +60,7 @@ public struct flatbuffers_goldens_Universe: FlatBufferObject, Verifiable { } public var age: Double { let o = _accessor.offset(VTOFFSET.age.v); return o == 0 ? 0.0 : _accessor.readBuffer(of: Double.self, at: o) } - public var hasGalaxies: Bool { let o = _accessor.offset(VTOFFSET.galaxies.v); return o == 0 ? false : true } - public var galaxiesCount: Int32 { let o = _accessor.offset(VTOFFSET.galaxies.v); return o == 0 ? 0 : _accessor.vector(count: o) } - public func galaxies(at index: Int32) -> flatbuffers_goldens_Galaxy? { let o = _accessor.offset(VTOFFSET.galaxies.v); return o == 0 ? nil : flatbuffers_goldens_Galaxy(_accessor.bb, o: _accessor.indirect(_accessor.vector(at: o) + index * 4)) } + public var galaxies: FlatbufferVector { return _accessor.vector(at: VTOFFSET.galaxies.v, byteSize: 4) } public static func startUniverse(_ fbb: inout FlatBufferBuilder) -> UOffset { fbb.startTable(with: 2) } public static func add(age: Double, _ fbb: inout FlatBufferBuilder) { fbb.add(element: age, def: 0.0, at: VTOFFSET.age.p) } public static func addVectorOf(galaxies: Offset, _ fbb: inout FlatBufferBuilder) { fbb.add(offset: galaxies, at: VTOFFSET.galaxies.p) } diff --git a/src/bfbs_gen_lua.cpp b/src/bfbs_gen_lua.cpp index 7e1e372b4d6..153a7882efc 100644 --- a/src/bfbs_gen_lua.cpp +++ b/src/bfbs_gen_lua.cpp @@ -694,10 +694,12 @@ class LuaBfbsGenerator : public BaseBfbsGenerator { std::replace(path.begin(), path.end(), '.', '/'); } - // TODO(derekbailey): figure out a save file without depending on util.h - EnsureDirExists(path); - const std::string file_name = - options_.output_path + path + "/" + namer_.File(name); + // Create the full directory under the configured output path. Use + // ConCatPathFileName to safely join path components (it will add the + // trailing separator if needed and strip a leading './'). + const std::string full_dir = ConCatPathFileName(options_.output_path, path); + EnsureDirExists(full_dir); + const std::string file_name = ConCatPathFileName(full_dir, namer_.File(name)); SaveFile(file_name.c_str(), code, false); } diff --git a/src/bfbs_gen_nim.cpp b/src/bfbs_gen_nim.cpp index ec72930e684..d4e77999f30 100644 --- a/src/bfbs_gen_nim.cpp +++ b/src/bfbs_gen_nim.cpp @@ -714,10 +714,12 @@ class NimBfbsGenerator : public BaseBfbsGenerator { std::replace(path.begin(), path.end(), '.', '/'); } - // TODO(derekbailey): figure out a save file without depending on util.h - EnsureDirExists(path); - const std::string file_name = - options_.output_path + path + "/" + namer_.File(name); + // Create the full directory under the configured output path. Use + // ConCatPathFileName to safely join path components (it will add the + // trailing separator if needed and strip a leading './'). + const std::string full_dir = ConCatPathFileName(options_.output_path, path); + EnsureDirExists(full_dir); + const std::string file_name = ConCatPathFileName(full_dir, namer_.File(name)); SaveFile(file_name.c_str(), code, false); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 00000000000..325eba04a97 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,8 @@ +# Test that bfbs generators handle namespace-first schemas correctly +find_package(Python3 REQUIRED COMPONENTS Interpreter) + +add_test( + NAME test_bfbs_namespace_output + COMMAND ${Python3_EXECUTABLE} -m unittest test_bfbs_namespace_output.py -v + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +) \ No newline at end of file diff --git a/tests/test_bfbs_namespace_output.py b/tests/test_bfbs_namespace_output.py new file mode 100644 index 00000000000..63734f50194 --- /dev/null +++ b/tests/test_bfbs_namespace_output.py @@ -0,0 +1,61 @@ +import os +import subprocess +import tempfile +import unittest + +class TestBfbsGeneratorNamespaceOutput(unittest.TestCase): + def test_lua_generator_respects_output_path_with_namespace_first_line(self): + # Schema that starts with a namespace on the first line (no leading comments) + schema = """namespace My.Game; + +table Monster { + id:int; +} + +root_type Monster; +""" + with tempfile.TemporaryDirectory() as tmpdir: + schema_path = os.path.join(tmpdir, "schema.fbs") + with open(schema_path, "w") as f: + f.write(schema) + + # Run the built flatc binary. Locate repository root relative to + # this test file so the test works regardless of the current + # working directory used by the test runner. + repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) + flatc_bin = os.path.join(repo_root, "build", "flatc") + cmd = [flatc_bin, "--lua", "-o", tmpdir, schema_path] + res = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + self.assertEqual(res.returncode, 0, f"flatc failed: {res.stderr.decode()}") + + # Expected generated file should be under tmpdir/My/Game/Monster.lua + expected_path = os.path.join(tmpdir, "My", "Game", "Monster.lua") + self.assertTrue(os.path.exists(expected_path), f"Expected file not found: {expected_path}") + + def test_nim_generator_respects_output_path_with_namespace_first_line(self): + # Same schema, but ensure the Nim bfbs generator writes under the + # configured output path. + schema = """namespace My.Game; + +table Monster { + id:int; +} + +root_type Monster; +""" + with tempfile.TemporaryDirectory() as tmpdir: + schema_path = os.path.join(tmpdir, "schema.fbs") + with open(schema_path, "w") as f: + f.write(schema) + + repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) + flatc_bin = os.path.join(repo_root, "build", "flatc") + cmd = [flatc_bin, "--nim", "-o", tmpdir, schema_path] + res = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + self.assertEqual(res.returncode, 0, f"flatc failed: {res.stderr.decode()}") + + expected_path = os.path.join(tmpdir, "My", "Game", "Monster.nim") + self.assertTrue(os.path.exists(expected_path), f"Expected file not found: {expected_path}") + +if __name__ == '__main__': + unittest.main()