Skip to content

Commit

Permalink
Add object lifetime tests to validate that C++ and Swift objects are …
Browse files Browse the repository at this point in the history
…destroyed (#428)
  • Loading branch information
tristanlabelle authored Dec 10, 2024
1 parent 97274d2 commit 48d49e1
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 2 deletions.
62 changes: 62 additions & 0 deletions InteropTests/Tests/LifetimeTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import XCTest
import WindowsRuntime
import WinRTComponent

class LifetimeTests: WinRTTestCase {
func testActivatedObjectDestroyedWhenUnreferencedFromSwift() throws {
var destroyed: Bool = false
withExtendedLifetime(try DestructionCallback { destroyed = true }) {
XCTAssertFalse(destroyed)
}
XCTAssertTrue(destroyed)
}

func testActivatedObjectDestroyedWhenUnreferencedFromWinRT() throws {
var destroyed: Bool = false
let objectReferencer = try ObjectReferencer(
try DestructionCallback { destroyed = true })
XCTAssertFalse(destroyed)
try objectReferencer.clear()
XCTAssertTrue(destroyed)
}

class ComposedDestructionCallback: DestructionCallback, @unchecked Sendable {
private let deinitCallback: () -> Void

public init(winRT: @escaping () -> Void, swift: @escaping () -> Void) throws {
self.deinitCallback = swift
try super.init(winRT)
}

deinit {
deinitCallback()
}
}

func testComposedObjectDestroyedWhenUnreferencedFromSwift() throws {
var destroyed: Bool = false
var deinited: Bool = false
withExtendedLifetime(try ComposedDestructionCallback(
winRT: { destroyed = true },
swift: { deinited = true })) {
XCTAssertFalse(destroyed)
XCTAssertFalse(deinited)
}
XCTAssertTrue(destroyed)
XCTAssertTrue(deinited)
}

func testComposedObjectDestroyedWhenUnreferencedFromWinRT() throws {
var destroyed: Bool = false
var deinited: Bool = false
let objectReferencer = try ObjectReferencer(
try ComposedDestructionCallback(
winRT: { destroyed = true },
swift: { deinited = true }))
XCTAssertFalse(destroyed)
XCTAssertFalse(deinited)
try objectReferencer.clear()
XCTAssertTrue(destroyed)
XCTAssertTrue(deinited)
}
}
21 changes: 21 additions & 0 deletions InteropTests/WinRTComponent/Dll/DestructionCallback.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#include "pch.h"
#include "DestructionCallback.g.h"

namespace winrt::WinRTComponent::implementation
{
struct DestructionCallback : DestructionCallbackT<DestructionCallback>
{
DestructionCallback(winrt::WinRTComponent::MinimalDelegate const& callback) : m_callback(callback) {}
~DestructionCallback() { m_callback(); }

winrt::WinRTComponent::MinimalDelegate m_callback;
};
}
namespace winrt::WinRTComponent::factory_implementation
{
struct DestructionCallback : DestructionCallbackT<DestructionCallback, implementation::DestructionCallback>
{
};
}

#include "DestructionCallback.g.cpp"
10 changes: 10 additions & 0 deletions InteropTests/WinRTComponent/IDL/DestructionCallback.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#include "MinimalTypes.idl"

namespace WinRTComponent
{
[default_interface]
unsealed runtimeclass DestructionCallback
{
DestructionCallback(MinimalDelegate callback);
};
}
4 changes: 2 additions & 2 deletions InteropTests/WinRTComponent/IDL/WinRTComponent.idl
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// Includes all .idl files so we have a single <Midl Include="All.idl"> entry in the vcxproj.
// This speeds up the build by avoiding multiple midlrt calls followed by an mdmerge step.
// Includes all .idl files so we can call midlrt only once and avoid slow mdmerge steps.
#include "Arrays.idl"
#include "ByteBuffers.idl"
#include "ClassInheritance.idl"
#include "Collections.idl"
#include "DateTimes.idl"
#include "DestructionCallback.idl"
#include "DocumentationComments.idl"
#include "Enums.idl"
#include "Errors.idl"
Expand Down

0 comments on commit 48d49e1

Please sign in to comment.