blob: 01c5f0e69bcb4946041384032f13cfe5de224079 [file]
// Protocol Buffers - Google's data interchange format
// Copyright 2026 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
// The only test in its test targets (macos_ and ios_global_extension_registry_
// process_test), so it runs in a fresh process in which nothing has used
// +[GPBExtensionRegistry globalExtensionRegistry] yet. That makes it possible
// to check, deterministically, that the global registry reads no Mach-O
// section before its first lookup, and to load a dynamic library after that
// first lookup.
#import <XCTest/XCTest.h>
#import <dlfcn.h>
#import "GPBDescriptor_PackagePrivate.h"
#import "GPBExtensionRegistry.h"
#import "GPBUtilities_PackagePrivate.h"
#import "objectivec/Tests/Unittest.pbobjc.h"
// Enum constants, so they can be used in the static initializer.
enum {
// The extension in libGPBTestDynamicExtensionImage.dylib
// (GPBTestDynamicExtensionImage.m).
kDynamicImageExtensionFieldNumber = 30101,
};
typedef GPBExtensionDescriptor *(*GPBTestExtensionFunction)(void);
typedef GPBExtensionSectionEntry *(*GPBTestExtensionEntryFunction)(void);
@interface GPBGlobalExtensionRegistryProcessTest : XCTestCase
@end
@implementation GPBGlobalExtensionRegistryProcessTest
// One test method, because the steps depend on the process wide state left by
// the previous ones.
- (void)testGlobalExtensionRegistryLifecycle {
GPBDescriptor *extendedDescriptor = [TestAllExtensions descriptor];
// 1. Getting the global registry reads no section.
XCTAssertFalse(GPBGlobalExtensionRegistryHasStartedIndexing());
id<GPBExtensionRegistry> globalRegistry = [GPBExtensionRegistry globalExtensionRegistry];
XCTAssertFalse(GPBGlobalExtensionRegistryHasStartedIndexing());
// 2. The first lookup on the global registry indexes the loaded images. The
// dynamic image is not loaded yet.
XCTAssertNil([globalRegistry extensionForDescriptor:extendedDescriptor
fieldNumber:kDynamicImageExtensionFieldNumber]);
XCTAssertTrue(GPBGlobalExtensionRegistryHasStartedIndexing());
// 3. Load the dynamic image. It resolves the protobuf runtime and
// TestAllExtensions from this test bundle through the flat namespace, so
// first make the (already loaded) test bundle's symbols globally visible.
NSBundle *testBundle = [NSBundle bundleForClass:[self class]];
NSString *testBundleExecutablePath = testBundle.executablePath;
XCTAssertNotNil(testBundleExecutablePath);
void *testBundleHandle = dlopen(testBundleExecutablePath.fileSystemRepresentation,
RTLD_NOW | RTLD_NOLOAD | RTLD_GLOBAL);
XCTAssertTrue(testBundleHandle != NULL, @"%s", dlerror());
NSString *imagePath = [testBundle pathForResource:@"libGPBTestDynamicExtensionImage"
ofType:@"dylib"];
XCTAssertNotNil(imagePath);
if (imagePath == nil) {
return;
}
// Never closed: images with Objective-C references are not unloaded.
void *imageHandle = dlopen(imagePath.fileSystemRepresentation, RTLD_NOW | RTLD_LOCAL);
XCTAssertTrue(imageHandle != NULL, @"%s", dlerror());
if (imageHandle == NULL) {
return;
}
GPBTestExtensionEntryFunction entryFunction = (GPBTestExtensionEntryFunction)dlsym(
imageHandle, "GPBTestDynamicExtensionImageExtensionEntry");
GPBTestExtensionFunction extensionFunction =
(GPBTestExtensionFunction)dlsym(imageHandle, "GPBTestDynamicExtensionImageExtension");
XCTAssertTrue(entryFunction != NULL);
XCTAssertTrue(extensionFunction != NULL);
if (entryFunction == NULL || extensionFunction == NULL) {
return;
}
GPBExtensionSectionEntry *entry = entryFunction();
// Loading (and indexing) the image created no descriptor.
XCTAssertTrue(entry->descriptor == nil);
// 4. The global registry now finds the image's extension: the instance its
// accessor returns.
GPBExtensionDescriptor *fromGlobalRegistry =
[globalRegistry extensionForDescriptor:extendedDescriptor
fieldNumber:kDynamicImageExtensionFieldNumber];
XCTAssertNotNil(fromGlobalRegistry);
XCTAssertTrue(entry->descriptor == fromGlobalRegistry); // ptr equality
XCTAssertTrue(extensionFunction() == fromGlobalRegistry); // ptr equality
XCTAssertTrue(fromGlobalRegistry.containingMessageClass == [TestAllExtensions class]);
XCTAssertEqual(fromGlobalRegistry.fieldNumber, (uint32_t)kDynamicImageExtensionFieldNumber);
}
@end