|
11 | 11 | #import <ExecuTorch/ExecuTorch.h> |
12 | 12 |
|
13 | 13 | @interface ModuleTestObjC : XCTestCase |
14 | | -// Covers -[ExecuTorchModule loadMethod:options:error:] and locks the |
15 | | -// "load_method consumes the borrow synchronously and does not cache it" |
16 | | -// invariant that the no-retain decision in the wrapper rests on. |
17 | | -// See the citation on ExecuTorchModule.mm's loadMethod:options:error: |
18 | | -// (module.cpp#L353-L409). If a future refactor caches `backend_options` |
19 | | -// on the Module, this test fails (the weak reference stays non-nil). |
20 | | -- (void)testLoadMethodWithOptionsDoesNotRetainOptions { |
21 | | - NSString *modelPath = [self requireFixture:@"add_coreml" ofType:@"pte"]; |
22 | | - if (!modelPath) return; |
23 | | - NSError *error = nil; |
24 | | - ExecuTorchModule *module = [[ExecuTorchModule alloc] initWithFilePath:modelPath]; |
25 | | - |
26 | | - __weak ExecuTorchBackendOptionsMap *weakOptions = nil; |
27 | | - @autoreleasepool { |
28 | | - ExecuTorchBackendOptionsMap *options = [ExecuTorchBackendOptionsMap mapWithOptions:@{ |
29 | | - @"CoreMLBackend": @[ |
30 | | - [ExecuTorchBackendOption optionWithKey:@"compute_unit" stringValue:@"cpu_and_gpu"], |
31 | | - ], |
32 | | - } error:&error]; |
33 | | - XCTAssertNotNil(options, @"%@", error); |
34 | | - weakOptions = options; |
35 | | - XCTAssertTrue([module loadMethod:@"forward" options:options error:&error], |
36 | | - @"%@", error); |
37 | | - XCTAssertTrue([module isMethodLoaded:@"forward"]); |
38 | | - } |
39 | | - // The local + any autoreleased refs have drained. If loadMethod:options: |
40 | | - // silently retained the map, weakOptions would still be live here. |
41 | | - XCTAssertNil(weakOptions, |
42 | | - @"loadMethod:options: must not retain the map (load_method consumes " |
43 | | - @"it synchronously). See module.cpp load_method borrow contract."); |
44 | | - |
45 | | - // The loaded method must still run — it reads from _module->methods_ |
46 | | - // (populated during loadMethod:options:), not from the options map. |
47 | | - ExecuTorchTensor *one = |
48 | | - [[ExecuTorchTensor alloc] initWithScalars:@[@1.0f] dataType:ExecuTorchDataTypeFloat]; |
49 | | - NSArray<ExecuTorchValue *> *outputs = |
50 | | - [module forwardWithTensors:@[one, one] error:&error]; |
51 | | - XCTAssertNotNil(outputs, @"%@", error); |
52 | | - |
53 | | - __block float result = NAN; |
54 | | - [outputs.firstObject.tensorValue |
55 | | - bytesWithHandler:^(const void *bytes, NSInteger count, ExecuTorchDataType dt) { |
56 | | - if (dt == ExecuTorchDataTypeFloat && count >= 1) { |
57 | | - result = ((const float *)bytes)[0]; |
58 | | - } |
59 | | - }]; |
60 | | - XCTAssertEqual(result, 2.0f); |
61 | | -} |
62 | | - |
63 | 14 | @end |
64 | 15 |
|
65 | 16 | @implementation ModuleTestObjC |
@@ -198,4 +149,53 @@ - (void)testBackendOptionsMapReusedAcrossModules { |
198 | 149 | } |
199 | 150 | } |
200 | 151 |
|
| 152 | +// Covers -[ExecuTorchModule loadMethod:options:error:] and locks the |
| 153 | +// "load_method consumes the borrow synchronously and does not cache it" |
| 154 | +// invariant that the no-retain decision in the wrapper rests on. |
| 155 | +// See the citation on ExecuTorchModule.mm's loadMethod:options:error: |
| 156 | +// (module.cpp#L353-L409). If a future refactor caches `backend_options` |
| 157 | +// on the Module, this test fails (the weak reference stays non-nil). |
| 158 | +- (void)testLoadMethodWithOptionsDoesNotRetainOptions { |
| 159 | + NSString *modelPath = [self requireFixture:@"add_coreml" ofType:@"pte"]; |
| 160 | + if (!modelPath) return; |
| 161 | + NSError *error = nil; |
| 162 | + ExecuTorchModule *module = [[ExecuTorchModule alloc] initWithFilePath:modelPath]; |
| 163 | + |
| 164 | + __weak ExecuTorchBackendOptionsMap *weakOptions = nil; |
| 165 | + @autoreleasepool { |
| 166 | + ExecuTorchBackendOptionsMap *options = [ExecuTorchBackendOptionsMap mapWithOptions:@{ |
| 167 | + @"CoreMLBackend": @[ |
| 168 | + [ExecuTorchBackendOption optionWithKey:@"compute_unit" stringValue:@"cpu_and_gpu"], |
| 169 | + ], |
| 170 | + } error:&error]; |
| 171 | + XCTAssertNotNil(options, @"%@", error); |
| 172 | + weakOptions = options; |
| 173 | + XCTAssertTrue([module loadMethod:@"forward" options:options error:&error], |
| 174 | + @"%@", error); |
| 175 | + XCTAssertTrue([module isMethodLoaded:@"forward"]); |
| 176 | + } |
| 177 | + // The local + any autoreleased refs have drained. If loadMethod:options: |
| 178 | + // silently retained the map, weakOptions would still be live here. |
| 179 | + XCTAssertNil(weakOptions, |
| 180 | + @"loadMethod:options: must not retain the map (load_method consumes " |
| 181 | + @"it synchronously). See module.cpp load_method borrow contract."); |
| 182 | + |
| 183 | + // The loaded method must still run — it reads from _module->methods_ |
| 184 | + // (populated during loadMethod:options:), not from the options map. |
| 185 | + ExecuTorchTensor *one = |
| 186 | + [[ExecuTorchTensor alloc] initWithScalars:@[@1.0f] dataType:ExecuTorchDataTypeFloat]; |
| 187 | + NSArray<ExecuTorchValue *> *outputs = |
| 188 | + [module forwardWithTensors:@[one, one] error:&error]; |
| 189 | + XCTAssertNotNil(outputs, @"%@", error); |
| 190 | + |
| 191 | + __block float result = NAN; |
| 192 | + [outputs.firstObject.tensorValue |
| 193 | + bytesWithHandler:^(const void *bytes, NSInteger count, ExecuTorchDataType dt) { |
| 194 | + if (dt == ExecuTorchDataTypeFloat && count >= 1) { |
| 195 | + result = ((const float *)bytes)[0]; |
| 196 | + } |
| 197 | + }]; |
| 198 | + XCTAssertEqual(result, 2.0f); |
| 199 | +} |
| 200 | + |
201 | 201 | @end |
0 commit comments