@@ 3,6 3,7 @@ package gen
import (
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
+ "google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/runtime/protoimpl"
"google.golang.org/protobuf/types/descriptorpb"
)
@@ 19,6 20,16 @@ var E_LuaPackage = &protoimpl.ExtensionInfo{
Filename: "tarantool/tarantool.proto",
}
+// Register the extension in the global type registry so that
+// proto.GetExtension can resolve it on FileOptions parsed by protoc-gen-go's
+// plugin scaffolding. Without this, the option's value lands in the message's
+// unknown fields and GetExtension returns the zero value ("").
+func init() {
+ if err := protoregistry.GlobalTypes.RegisterExtension(E_LuaPackage); err != nil {
+ panic("tarantool-protobuf: register E_LuaPackage: " + err.Error())
+ }
+}
+
// luaPackageOption returns the value of (tarantool.lua_package) on the file
// options, or "" when unset.
func luaPackageOption(f protoreflect.FileDescriptor) string {
@@ 0,0 1,108 @@
+-- Regression test for (tarantool.lua_package) file option.
+--
+-- The option lives on FileOptions as extension field 60001 and overrides
+-- the default `<pkg>.<file>_pb` Lua require path. The plugin parses the
+-- extension via protoreflect/protoregistry, so this test guards against
+-- the option silently becoming a no-op when the extension is not
+-- registered in the global type registry (the failure mode is that
+-- proto.GetExtension returns "" and the override is lost).
+--
+-- Coverage:
+-- * The on-disk output path follows `lua_package`, not `<pkg>/<file>_pb`.
+-- * Cross-file `import "other.proto"` rewrites to `require("<lua_package>")`.
+
+local t = require('luatest')
+local fio = require('fio')
+
+local g = t.group('codegen_lua_package')
+
+local REPO_ROOT = fio.abspath(fio.pathjoin(
+ fio.dirname(debug.getinfo(1, 'S').source:sub(2)), '..'))
+local OPTIONS_DIR = fio.pathjoin(REPO_ROOT, 'options')
+local PLUGIN = fio.pathjoin(REPO_ROOT, 'protoc-gen-tarantool')
+
+local function spit(path, content)
+ local f = assert(io.open(path, 'wb'))
+ f:write(content)
+ f:close()
+end
+
+local function slurp(path)
+ local f = assert(io.open(path, 'rb'))
+ local s = f:read('*a')
+ f:close()
+ return s
+end
+
+local function ensure_plugin()
+ if fio.path.exists(PLUGIN) then return end
+ local cmd = string.format('cd %q && go build -o %s ./cmd/protoc-gen-tarantool',
+ REPO_ROOT, fio.basename(PLUGIN))
+ assert(os.execute(cmd) == 0 or os.execute(cmd) == true,
+ 'failed to build plugin: ' .. cmd)
+end
+
+local SAMPLE_PROTO = [[
+syntax = "proto3";
+package sample;
+import "tarantool/tarantool.proto";
+option (tarantool.lua_package) = "myapp.proto.sample";
+message Foo { int32 x = 1; }
+]]
+
+local OTHER_PROTO = [[
+syntax = "proto3";
+package other;
+import "tarantool/tarantool.proto";
+import "sample.proto";
+option (tarantool.lua_package) = "myapp.proto.other";
+message Bar { sample.Foo foo = 1; }
+]]
+
+local function run_plugin(mode)
+ local tmp = fio.tempdir()
+ local proto_dir = fio.pathjoin(tmp, 'proto')
+ local out_dir = fio.pathjoin(tmp, 'out')
+ assert(fio.mkdir(proto_dir))
+ assert(fio.mkdir(out_dir))
+ spit(fio.pathjoin(proto_dir, 'sample.proto'), SAMPLE_PROTO)
+ spit(fio.pathjoin(proto_dir, 'other.proto'), OTHER_PROTO)
+
+ local cmd = string.format(
+ 'protoc --plugin=%q --tarantool_out=%q --tarantool_opt=mode=%s '
+ ..'-I %q -I %q %q %q',
+ PLUGIN, out_dir, mode, proto_dir, OPTIONS_DIR,
+ fio.pathjoin(proto_dir, 'sample.proto'),
+ fio.pathjoin(proto_dir, 'other.proto'))
+ local ok = os.execute(cmd)
+ assert(ok == 0 or ok == true, 'plugin failed: ' .. cmd)
+ return out_dir
+end
+
+g.before_all(function()
+ ensure_plugin()
+end)
+
+for _, mode in ipairs({'full', 'runtime'}) do
+ g['test_output_path_honors_lua_package_'..mode] = function()
+ local out = run_plugin(mode)
+ t.assert(fio.path.exists(fio.pathjoin(out, 'myapp', 'proto', 'sample.lua')),
+ 'expected sample.lua under lua_package path, got: '
+ .. table.concat(fio.glob(fio.pathjoin(out, '**', '*.lua')) or {}, ', '))
+ t.assert(fio.path.exists(fio.pathjoin(out, 'myapp', 'proto', 'other.lua')),
+ 'expected other.lua under lua_package path')
+ -- Default `<pkg>/<file>_pb.lua` layout must NOT be present —
+ -- that's the regression signature (extension dropped, fallback used).
+ t.assert_not(fio.path.exists(fio.pathjoin(out, 'sample', 'sample_pb.lua')),
+ 'fallback path was emitted — lua_package option was ignored')
+ end
+
+ g['test_cross_file_require_honors_lua_package_'..mode] = function()
+ local out = run_plugin(mode)
+ local body = slurp(fio.pathjoin(out, 'myapp', 'proto', 'other.lua'))
+ t.assert_str_contains(body, 'require("myapp.proto.sample")', false,
+ 'cross-file import should resolve via lua_package, not the default path')
+ t.assert_not(string.find(body, 'require("sample.sample_pb")', 1, true),
+ 'default require path leaked into cross-file import')
+ end
+end