vulkan_layer/
test_utils.rs

1// Copyright 2023 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Utilities used in the integration tests of this crate.
16//!
17//! The Vulkan layer essentially provides a bunch of function pointers to the loader. To mock
18//! multiple layers in the same process, it is required to use different types so different
19//! function pointers will be generated. To avoid extremely long compile time, we shouldn't create
20//! a new layer type for every test case. Instead we run test cases in different processes, and
21//! share the same layer type as possible. If a test case needs multiple layers at the same time,
22//! multiple layer types should be used.
23//!
24//! Follow the following steps to write a unit test:
25//! 1. Use [`TestGlobal::builder`] to construct the static resources needed to mock one layer. Mocks
26//!    to static methods can be set with different builder functions.
27//! 2. Call [`TestGlobal::create_context`] to set up the scope for expectations.
28//! 3. Use [`TestLayer`] as a layer implementation.
29
30use crate::{
31    DeviceInfo, Global, GlobalHooksInfo, InstanceInfo, Layer, LayerManifest, LayerVulkanCommand,
32};
33use ash::vk;
34use mockall::mock;
35use once_cell::sync::Lazy;
36use std::{
37    any::Any,
38    borrow::Borrow,
39    collections::HashMap,
40    marker::PhantomData,
41    ops::Deref,
42    sync::{Arc, Mutex, MutexGuard, Weak},
43};
44
45pub use crate::bindings::vk_layer::{
46    VkLayerDeviceCreateInfo, VkLayerDeviceLink, VkLayerFunction, VkLayerInstanceCreateInfo,
47};
48
49mod device_hooks_mock;
50mod global_hooks_mock;
51mod instance_hooks_mock;
52
53use device_hooks_mock::MockDeviceHooks;
54use global_hooks_mock::MockGlobalHooks;
55use instance_hooks_mock::MockInstanceHooks;
56
57type Deleter<T> = Box<dyn FnOnce(&mut T) + Send + Sync>;
58
59/// A wrapper for T but with a user defined deleter. 'Del' stands for 'deleter'.
60///
61/// The deleter will be called on drop. Used to mock the [`Drop`] trait.
62pub struct Del<T> {
63    data: T,
64    deleter: Option<Deleter<T>>,
65}
66
67impl<T> Del<T> {
68    /// Constructs a new [`Del<T>`] with a custom deleter.
69    pub fn new(data: T, deleter: impl FnOnce(&mut T) + Send + Sync + 'static) -> Self {
70        Del {
71            data,
72            deleter: Some(Box::new(deleter)),
73        }
74    }
75}
76
77impl<T> Deref for Del<T> {
78    type Target = T;
79
80    fn deref(&self) -> &Self::Target {
81        &self.data
82    }
83}
84
85impl<T> Drop for Del<T> {
86    fn drop(&mut self) {
87        (self.deleter.take().unwrap())(&mut self.data);
88    }
89}
90
91impl<T> AsRef<T> for Del<T> {
92    fn as_ref(&self) -> &T {
93        self.deref()
94    }
95}
96
97/// A thread-safe reference-counting pointer, but with a custom deleter.
98pub struct ArcDel<T>(pub Arc<Del<T>>);
99
100impl<T> ArcDel<T> {
101    /// Constructs a new [`ArcDel<T>`] with a custom deleter.
102    pub fn new(data: T, deleter: impl FnOnce(&mut T) + Send + Sync + 'static) -> Self {
103        ArcDel(Arc::new(Del::new(data, deleter)))
104    }
105}
106
107impl<T> Borrow<T> for ArcDel<T> {
108    fn borrow(&self) -> &T {
109        &self.0.data
110    }
111}
112
113impl<T: Default> Default for ArcDel<T> {
114    fn default() -> Self {
115        Self(Arc::new(Del::new(Default::default(), |_| {})))
116    }
117}
118
119impl<T> From<Del<T>> for ArcDel<T> {
120    fn from(value: Del<T>) -> Self {
121        Self(Arc::new(value))
122    }
123}
124
125impl<T> Deref for ArcDel<T> {
126    type Target = T;
127    fn deref(&self) -> &Self::Target {
128        self.0.as_ref()
129    }
130}
131
132impl<T> AsRef<T> for ArcDel<T> {
133    fn as_ref(&self) -> &T {
134        self.0.as_ref()
135    }
136}
137
138impl<T> Clone for ArcDel<T> {
139    fn clone(&self) -> Self {
140        Self(self.0.clone())
141    }
142}
143
144mock! {
145    pub Drop {}
146    impl Drop for Drop {
147        fn drop(&mut self);
148    }
149}
150
151/// A set of interfaces that the integration tests are interested to mock.
152pub trait TestLayerMock: 'static {
153    /// The associated type that implements the [`Layer`] trait. Should be a specialized
154    /// [`TestLayer`].
155    type TestLayerType: Layer;
156
157    /// Used to mock [`Layer::global_instance`].
158    fn instance() -> &'static Global<Self::TestLayerType>;
159
160    /// Used to obtain the singleton of this object.
161    ///
162    /// This interface is used to obtain the mock object in the static methods like
163    /// [`DeviceInfo::hooked_commands`]. The mock object can't be part of [`Global`], because we
164    /// want to allow the mocked interfaces to be called during or before
165    /// the initialization of [`Global`].
166    fn mock() -> &'static Self;
167
168    /// Used to mock [`Layer::manifest`].
169    fn manifest(&self) -> LayerManifest;
170
171    /// Used to mock [`GlobalHooksInfo::hooked_commands`].
172    fn hooked_global_commands(&self) -> &[LayerVulkanCommand];
173
174    /// Used to mock [`InstanceInfo::hooked_commands`].
175    fn hooked_instance_commands(&self) -> &[LayerVulkanCommand];
176
177    /// Used to mock [`DeviceInfo::hooked_commands`].
178    fn hooked_device_commands(&self) -> &[LayerVulkanCommand];
179}
180
181/// A mock struct that implements the [`GlobalHooksInfo`] trait.
182///
183/// The `T` type parameter is a [`TestLayerMock`] type that provides the mock of
184/// [`GlobalHooksInfo::hooked_commands`].
185#[derive(Default)]
186pub struct MockGlobalHooksInfo<T: TestLayerMock> {
187    /// The mock of the [`GlobalHooks`][crate::GlobalHooks] trait.
188    pub mock_hooks: Mutex<MockGlobalHooks>,
189    _marker: PhantomData<fn(T)>,
190}
191
192impl<T: TestLayerMock> GlobalHooksInfo for MockGlobalHooksInfo<T> {
193    type HooksType = MockGlobalHooks;
194    type HooksRefType<'a> = MutexGuard<'a, MockGlobalHooks>;
195    fn hooked_commands() -> &'static [LayerVulkanCommand] {
196        T::mock().hooked_global_commands()
197    }
198
199    fn hooks(&self) -> Self::HooksRefType<'_> {
200        self.mock_hooks.lock().unwrap()
201    }
202}
203
204/// A mock struct that implements the [`InstanceInfo`] trait.
205///
206/// The `T` type parameter is a [`TestLayerMock`] type that provides the mock of
207/// [`InstanceInfo::hooked_commands`].
208#[derive(Default)]
209pub struct MockInstanceInfo<T: TestLayerMock> {
210    /// The mock of the [`InstanceHooks`][crate::InstanceHooks].
211    pub mock_hooks: Mutex<MockInstanceHooks>,
212    mock_drop: Mutex<Option<MockDrop>>,
213    _marker: PhantomData<fn(T)>,
214}
215
216impl<T: TestLayerMock> MockInstanceInfo<T> {
217    /// Mock the drop behavior.
218    ///
219    /// The expectations can be set through the `f` argument. If this method is never called, the
220    /// struct will be dropped as if the drop is not mocked, i.e. won't check how drop is called.
221    pub fn with_mock_drop(&self, f: impl FnOnce(&mut MockDrop)) {
222        let mut mock_drop = self.mock_drop.lock().unwrap();
223        let mock_drop = mock_drop.get_or_insert_with(Default::default);
224        f(mock_drop);
225    }
226}
227
228impl<T: TestLayerMock> InstanceInfo for MockInstanceInfo<T> {
229    type HooksType = MockInstanceHooks;
230    type HooksRefType<'a> = MutexGuard<'a, MockInstanceHooks>;
231    fn hooked_commands() -> &'static [LayerVulkanCommand] {
232        T::mock().hooked_instance_commands()
233    }
234
235    fn hooks(&self) -> Self::HooksRefType<'_> {
236        self.mock_hooks.lock().unwrap()
237    }
238}
239
240/// A mock struct that implements the [`DeviceInfo`] trait.
241///
242/// The `T` type parameter is a [`TestLayerMock`] type that provides the mock of
243/// [`DeviceInfo::hooked_commands`].
244#[derive(Default)]
245pub struct MockDeviceInfo<T: TestLayerMock> {
246    /// The mock of the [`DeviceHooks`][crate::DeviceHooks].
247    pub mock_hooks: Mutex<MockDeviceHooks>,
248    mock_drop: Mutex<Option<MockDrop>>,
249    _marker: PhantomData<fn(T)>,
250}
251
252impl<T: TestLayerMock> MockDeviceInfo<T> {
253    /// Mock the drop behavior.
254    ///
255    /// The expectations can be set through the `f` argument. If this method is never called, the
256    /// struct will be dropped as if the drop is not mocked, i.e. won't check how drop is called.
257    pub fn with_mock_drop(&self, f: impl FnOnce(&mut MockDrop)) {
258        let mut mock_drop = self.mock_drop.lock().unwrap();
259        let mock_drop = mock_drop.get_or_insert_with(Default::default);
260        f(mock_drop);
261    }
262}
263
264impl<T: TestLayerMock> DeviceInfo for MockDeviceInfo<T> {
265    type HooksType = MockDeviceHooks;
266    type HooksRefType<'a> = MutexGuard<'a, MockDeviceHooks>;
267
268    fn hooked_commands() -> &'static [LayerVulkanCommand] {
269        T::mock().hooked_device_commands()
270    }
271
272    fn hooks(&self) -> Self::HooksRefType<'_> {
273        self.mock_hooks.lock().unwrap()
274    }
275}
276
277/// Test layer tags to distinguish different [`TestLayer`]. Different `I` will result in different
278/// types.
279pub struct Tag<const I: usize>;
280
281/// A trait used to include all possible [`Tag<I>`].
282pub trait TestLayerTag: Sync + Send + 'static {}
283
284impl<const I: usize> TestLayerTag for Tag<I> {}
285
286/// The mock for the [`Layer`] trait.
287///
288/// Different `T` will result in different types, default to [`Tag<0>`]. [`TestGlobal<T>`] contains
289/// the static resources(e.g. [`Global<TestLayer<T>>`]) related to this mock layer.
290pub struct TestLayer<T: TestLayerTag = Tag<0>> {
291    global_hooks_info: MockGlobalHooksInfo<MockTestLayer<T>>,
292    instances: Mutex<HashMap<vk::Instance, Weak<Del<<Self as Layer>::InstanceInfo>>>>,
293    devices: Mutex<HashMap<vk::Device, Weak<Del<<Self as Layer>::DeviceInfo>>>>,
294    _marker: PhantomData<fn(T)>,
295}
296
297impl<T: TestLayerTag> TestLayer<T> {
298    /// Get the [`InstanceInfo`] mock from a `VkInstance`.
299    pub fn get_instance_info(
300        &self,
301        instance: vk::Instance,
302    ) -> Option<Arc<impl Deref<Target = <Self as Layer>::InstanceInfo>>> {
303        self.instances
304            .lock()
305            .unwrap()
306            .get(&instance)
307            .and_then(Weak::upgrade)
308    }
309
310    /// Get the [`DeviceInfo`] mock from a `VkDevice`.
311    pub fn get_device_info(
312        &self,
313        device: vk::Device,
314    ) -> Option<Arc<impl Deref<Target = <Self as Layer>::DeviceInfo>>> {
315        self.devices
316            .lock()
317            .unwrap()
318            .get(&device)
319            .and_then(Weak::upgrade)
320    }
321}
322
323impl<T: TestLayerTag> Default for TestLayer<T> {
324    fn default() -> Self {
325        Self {
326            global_hooks_info: Default::default(),
327            instances: Default::default(),
328            devices: Default::default(),
329            _marker: Default::default(),
330        }
331    }
332}
333
334impl<T: TestLayerTag> Layer for TestLayer<T> {
335    type GlobalHooksInfo = MockGlobalHooksInfo<MockTestLayer<T>>;
336    type InstanceInfo = MockInstanceInfo<MockTestLayer<T>>;
337    type DeviceInfo = MockDeviceInfo<MockTestLayer<T>>;
338    type InstanceInfoContainer = ArcDel<Self::InstanceInfo>;
339    type DeviceInfoContainer = ArcDel<Self::DeviceInfo>;
340
341    fn global_instance() -> impl std::ops::Deref<Target = Global<Self>> + 'static {
342        MockTestLayer::<T>::instance()
343    }
344
345    fn manifest() -> LayerManifest {
346        MockTestLayer::<T>::mock().manifest()
347    }
348
349    fn global_hooks_info(&self) -> &Self::GlobalHooksInfo {
350        &self.global_hooks_info
351    }
352
353    fn create_device_info(
354        &self,
355        _physical_device: vk::PhysicalDevice,
356        _create_info: &vk::DeviceCreateInfo,
357        _allocator: Option<&vk::AllocationCallbacks>,
358        device: Arc<ash::Device>,
359        _next_get_device_proc_addr: vk::PFN_vkGetDeviceProcAddr,
360    ) -> ArcDel<Self::DeviceInfo> {
361        let device_handle = device.handle();
362        let device_info = ArcDel::new(Default::default(), move |_| {
363            let layer = &Self::global_instance().layer_info;
364            layer.devices.lock().unwrap().remove(&device_handle);
365        });
366        self.devices
367            .lock()
368            .unwrap()
369            .insert(device.handle(), Arc::downgrade(&device_info.0));
370        device_info
371    }
372
373    fn create_instance_info(
374        &self,
375        _create_info: &vk::InstanceCreateInfo,
376        _allocator: Option<&vk::AllocationCallbacks>,
377        instance: Arc<ash::Instance>,
378        _next_get_instance_proc_addr: vk::PFN_vkGetInstanceProcAddr,
379    ) -> ArcDel<Self::InstanceInfo> {
380        let instance_handle = instance.handle();
381        let instance_info = ArcDel::new(Default::default(), move |_| {
382            let layer = &Self::global_instance().layer_info;
383            layer.instances.lock().unwrap().remove(&instance_handle);
384        });
385        self.instances
386            .lock()
387            .unwrap()
388            .insert(instance.handle(), Arc::downgrade(&instance_info.0));
389        instance_info
390    }
391}
392
393mock! {
394    pub TestLayer<T: TestLayerTag = Tag<0>> {}
395
396    impl<T: TestLayerTag> TestLayerMock for TestLayer<T> {
397        type TestLayerType = TestLayer<T>;
398
399        fn instance() -> &'static Global<TestLayer<T>>;
400        fn mock() -> &'static Self;
401
402        fn manifest(&self) -> LayerManifest;
403        fn hooked_global_commands(&self) -> &[LayerVulkanCommand];
404        fn hooked_instance_commands(&self) -> &[LayerVulkanCommand];
405        fn hooked_device_commands(&self) -> &[LayerVulkanCommand];
406    }
407}
408
409impl<T: TestLayerTag> MockTestLayer<T> {
410    /// Set the default behavior of the [`MockTestLayer`]: intercept no commands and a valid
411    /// [`LayerManifest`].
412    pub fn set_default_expectations(&mut self) {
413        self.expect_manifest()
414            .return_const(LayerManifest::test_default());
415        self.expect_hooked_global_commands().return_const(vec![]);
416        self.expect_hooked_instance_commands().return_const(vec![]);
417        self.expect_hooked_device_commands().return_const(vec![]);
418    }
419}
420
421/// The container of the static resources related to [`TestLayer<T>`].
422///
423/// Use the [`TestGlobal::builder`] to construct one.
424pub struct TestGlobal<T: TestLayerTag = Tag<0>> {
425    layer_mock: Lazy<MockTestLayer<T>>,
426    layer_global: Lazy<Global<TestLayer<T>>>,
427}
428
429/// The builder for [`TestGlobal`].
430pub struct TestGlobalBuilder<T: TestLayerTag = Tag<0>> {
431    layer_mock_builder: fn() -> MockTestLayer<T>,
432    layer_global_builder: fn() -> Global<TestLayer<T>>,
433}
434
435impl<T: TestLayerTag> TestGlobalBuilder<T> {
436    /// Specify how [`MockTestLayer`] should be created.
437    ///
438    /// Can be used to customize the behavior of pre-initialized methods, like
439    /// [`InstanceInfo::hooked_commands`].
440    ///
441    /// Pre-initialized methods are almost always called, so it is usually needed to call
442    /// [`MockTestLayer::set_default_expectations`] right before return to provide a meaningful
443    /// default behavior.
444    pub const fn set_layer_mock_builder(
445        self,
446        layer_mock_builder: fn() -> MockTestLayer<T>,
447    ) -> Self {
448        Self {
449            layer_mock_builder,
450            ..self
451        }
452    }
453
454    /// Specify how [`Global<TestLayer<T>>`] should be created.
455    ///
456    /// Can be used to customize the behavior and expectations of layer behaviors after [`Global`]
457    /// is initialized, especially the global commands, e.g.
458    /// [`GlobalHooks::create_instance`][crate::GlobalHooks::create_instance].
459    pub const fn set_layer_global_builder(
460        self,
461        layer_global_builder: fn() -> Global<TestLayer<T>>,
462    ) -> Self {
463        Self {
464            layer_global_builder,
465            ..self
466        }
467    }
468
469    /// Construct the [`TestGlobal<T>`].
470    ///
471    /// This is a const function, and can be directly used to initialize a static variable.
472    pub const fn build(&self) -> TestGlobal<T> {
473        TestGlobal {
474            layer_mock: Lazy::new(self.layer_mock_builder),
475            layer_global: Lazy::new(self.layer_global_builder),
476        }
477    }
478}
479
480impl<T: TestLayerTag> TestGlobal<T> {
481    /// Create the builder for initialization.
482    pub const fn builder() -> TestGlobalBuilder<T> {
483        TestGlobalBuilder {
484            layer_mock_builder: || {
485                let mut mock = MockTestLayer::<T>::default();
486                mock.set_default_expectations();
487                mock
488            },
489            layer_global_builder: Default::default,
490        }
491    }
492
493    /// Create a context for expectations for the static object.
494    pub fn create_context(&'static self) -> Box<dyn Any> {
495        let layer_mock_ctx = MockTestLayer::mock_context();
496        layer_mock_ctx.expect().return_const(&*self.layer_mock);
497        let layer_global_ctx = MockTestLayer::instance_context();
498        layer_global_ctx.expect().return_const(&*self.layer_global);
499        Box::new((layer_mock_ctx, layer_global_ctx))
500    }
501}
502
503/// A trait to provide an extra meaningful constructor for [`LayerManifest`].
504pub trait LayerManifestExt {
505    /// Create a [`LayerManifest`] with reasonable fields.
506    fn test_default() -> Self;
507}
508
509impl LayerManifestExt for LayerManifest {
510    fn test_default() -> Self {
511        Self {
512            name: "VK_LAYER_GOOGLE_test",
513            spec_version: vk::API_VERSION_1_1,
514            ..Default::default()
515        }
516    }
517}