Commit 41daf06e authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'linux_kselftest-kunit-6.8-rc1' of...

Merge tag 'linux_kselftest-kunit-6.8-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest

Pull KUnit updates from Shuah Khan:

 - a new feature that adds APIs for managing devices introducing a set
   of helper functions which allow devices (internally a struct
   kunit_device) to be created and managed by KUnit.

   These devices will be automatically unregistered on test exit. These
   helpers can either use a user-provided struct device_driver, or have
   one automatically created and managed by KUnit. In both cases, the
   device lives on a new kunit_bus.

 - changes to switch drm/tests to use kunit devices

 - several fixes and enhancements to attribute feature

 - changes to reorganize deferred action function introducing
   KUNIT_DEFINE_ACTION_WRAPPER

 - new feature adds ability to run tests after boot using debugfs

 - fixes and enhancements to string-stream-test:
     - parse ERR_PTR in string_stream_destroy()
     - unchecked dereference in bug fix in debugfs_print_results()
     - handling errors from alloc_string_stream()
     - NULL-dereference bug fix in kunit_init_suite()

* tag 'linux_kselftest-kunit-6.8-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest: (27 commits)
  kunit: Fix some comments which were mistakenly kerneldoc
  kunit: Protect string comparisons against NULL
  kunit: Add example of kunit_activate_static_stub() with pointer-to-function
  kunit: Allow passing function pointer to kunit_activate_static_stub()
  kunit: Fix NULL-dereference in kunit_init_suite() if suite->log is NULL
  kunit: Reset test->priv after each param iteration
  kunit: Add example for using test->priv
  drm/tests: Switch to kunit devices
  ASoC: topology: Replace fake root_device with kunit_device in tests
  overflow: Replace fake root_device with kunit_device
  fortify: test: Use kunit_device
  kunit: Add APIs for managing devices
  Documentation: Add debugfs docs with run after boot
  kunit: add ability to run tests after boot using debugfs
  kunit: add is_init test attribute
  kunit: add example suite to test init suites
  kunit: add KUNIT_INIT_TABLE to init linker section
  kunit: move KUNIT_TABLE out of INIT_DATA
  kunit: tool: add test for parsing attributes
  kunit: tool: fix parsing of test attributes
  ...
parents 5d09f61e 539e582a
...@@ -11,3 +11,12 @@ state on a per-test basis, register custom cleanup actions, and more. ...@@ -11,3 +11,12 @@ state on a per-test basis, register custom cleanup actions, and more.
.. kernel-doc:: include/kunit/resource.h .. kernel-doc:: include/kunit/resource.h
:internal: :internal:
Managed Devices
---------------
Functions for using KUnit-managed struct device and struct device_driver.
Include ``kunit/device.h`` to use these.
.. kernel-doc:: include/kunit/device.h
:internal:
...@@ -49,9 +49,52 @@ loaded. ...@@ -49,9 +49,52 @@ loaded.
The results will appear in TAP format in ``dmesg``. The results will appear in TAP format in ``dmesg``.
debugfs
=======
KUnit can be accessed from userspace via the debugfs filesystem (See more
information about debugfs at Documentation/filesystems/debugfs.rst).
If ``CONFIG_KUNIT_DEBUGFS`` is enabled, the KUnit debugfs filesystem is
mounted at /sys/kernel/debug/kunit. You can use this filesystem to perform
the following actions.
Retrieve Test Results
=====================
You can use debugfs to retrieve KUnit test results. The test results are
accessible from the debugfs filesystem in the following read-only file:
.. code-block :: bash
/sys/kernel/debug/kunit/<test_suite>/results
The test results are printed in a KTAP document. Note this document is separate
to the kernel log and thus, may have different test suite numbering.
Run Tests After Kernel Has Booted
=================================
You can use the debugfs filesystem to trigger built-in tests to run after
boot. To run the test suite, you can use the following command to write to
the ``/sys/kernel/debug/kunit/<test_suite>/run`` file:
.. code-block :: bash
echo "any string" > /sys/kernel/debugfs/kunit/<test_suite>/run
As a result, the test suite runs and the results are printed to the kernel
log.
However, this feature is not available with KUnit suites that use init data,
because init data may have been discarded after the kernel boots. KUnit
suites that use init data should be defined using the
kunit_test_init_section_suites() macro.
Also, you cannot use this feature to run tests concurrently. Instead a test
will wait to run until other tests have completed or failed.
.. note :: .. note ::
If ``CONFIG_KUNIT_DEBUGFS`` is enabled, KUnit test results will For test authors, to use this feature, tests will need to correctly initialise
be accessible from the ``debugfs`` filesystem (if mounted). and/or clean up any data, so the test runs correctly a second time.
They will be in ``/sys/kernel/debug/kunit/<test_suite>/results``, in
TAP format.
...@@ -428,3 +428,10 @@ This attribute indicates the name of the module associated with the test. ...@@ -428,3 +428,10 @@ This attribute indicates the name of the module associated with the test.
This attribute is automatically saved as a string and is printed for each suite. This attribute is automatically saved as a string and is printed for each suite.
Tests can also be filtered using this attribute. Tests can also be filtered using this attribute.
``is_init``
This attribute indicates whether the test uses init data or functions.
This attribute is automatically saved as a boolean and tests can also be
filtered using this attribute.
...@@ -651,12 +651,16 @@ For example: ...@@ -651,12 +651,16 @@ For example:
} }
Note that, for functions like device_unregister which only accept a single Note that, for functions like device_unregister which only accept a single
pointer-sized argument, it's possible to directly cast that function to pointer-sized argument, it's possible to automatically generate a wrapper
a ``kunit_action_t`` rather than writing a wrapper function, for example: with the ``KUNIT_DEFINE_ACTION_WRAPPER()`` macro, for example:
.. code-block:: C .. code-block:: C
kunit_add_action(test, (kunit_action_t *)&device_unregister, &dev); KUNIT_DEFINE_ACTION_WRAPPER(device_unregister, device_unregister_wrapper, struct device *);
kunit_add_action(test, &device_unregister_wrapper, &dev);
You should do this in preference to manually casting to the ``kunit_action_t`` type,
as casting function pointers will break Control Flow Integrity (CFI).
``kunit_add_action`` can fail if, for example, the system is out of memory. ``kunit_add_action`` can fail if, for example, the system is out of memory.
You can use ``kunit_add_action_or_reset`` instead which runs the action You can use ``kunit_add_action_or_reset`` instead which runs the action
...@@ -793,3 +797,53 @@ structures as shown below: ...@@ -793,3 +797,53 @@ structures as shown below:
KUnit is not enabled, or if no test is running in the current task, it will do KUnit is not enabled, or if no test is running in the current task, it will do
nothing. This compiles down to either a no-op or a static key check, so will nothing. This compiles down to either a no-op or a static key check, so will
have a negligible performance impact when no test is running. have a negligible performance impact when no test is running.
Managing Fake Devices and Drivers
---------------------------------
When testing drivers or code which interacts with drivers, many functions will
require a ``struct device`` or ``struct device_driver``. In many cases, setting
up a real device is not required to test any given function, so a fake device
can be used instead.
KUnit provides helper functions to create and manage these fake devices, which
are internally of type ``struct kunit_device``, and are attached to a special
``kunit_bus``. These devices support managed device resources (devres), as
described in Documentation/driver-api/driver-model/devres.rst
To create a KUnit-managed ``struct device_driver``, use ``kunit_driver_create()``,
which will create a driver with the given name, on the ``kunit_bus``. This driver
will automatically be destroyed when the corresponding test finishes, but can also
be manually destroyed with ``driver_unregister()``.
To create a fake device, use the ``kunit_device_register()``, which will create
and register a device, using a new KUnit-managed driver created with ``kunit_driver_create()``.
To provide a specific, non-KUnit-managed driver, use ``kunit_device_register_with_driver()``
instead. Like with managed drivers, KUnit-managed fake devices are automatically
cleaned up when the test finishes, but can be manually cleaned up early with
``kunit_device_unregister()``.
The KUnit devices should be used in preference to ``root_device_register()``, and
instead of ``platform_device_register()`` in cases where the device is not otherwise
a platform device.
For example:
.. code-block:: c
#include <kunit/device.h>
static void test_my_device(struct kunit *test)
{
struct device *fake_device;
const char *dev_managed_string;
// Create a fake device.
fake_device = kunit_device_register(test, "my_device");
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fake_device)
// Pass it to functions which need a device.
dev_managed_string = devm_kstrdup(fake_device, "Hello, World!");
// Everything is cleaned up automatically when the test ends.
}
\ No newline at end of file
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include <drm/drm_kunit_helpers.h> #include <drm/drm_kunit_helpers.h>
#include <drm/drm_managed.h> #include <drm/drm_managed.h>
#include <kunit/device.h>
#include <kunit/resource.h> #include <kunit/resource.h>
#include <linux/device.h> #include <linux/device.h>
...@@ -15,40 +16,6 @@ ...@@ -15,40 +16,6 @@
static const struct drm_mode_config_funcs drm_mode_config_funcs = { static const struct drm_mode_config_funcs drm_mode_config_funcs = {
}; };
static int fake_probe(struct platform_device *pdev)
{
return 0;
}
static struct platform_driver fake_platform_driver = {
.probe = fake_probe,
.driver = {
.name = KUNIT_DEVICE_NAME,
},
};
static void kunit_action_platform_driver_unregister(void *ptr)
{
struct platform_driver *drv = ptr;
platform_driver_unregister(drv);
}
static void kunit_action_platform_device_put(void *ptr)
{
struct platform_device *pdev = ptr;
platform_device_put(pdev);
}
static void kunit_action_platform_device_del(void *ptr)
{
struct platform_device *pdev = ptr;
platform_device_del(pdev);
}
/** /**
* drm_kunit_helper_alloc_device - Allocate a mock device for a KUnit test * drm_kunit_helper_alloc_device - Allocate a mock device for a KUnit test
* @test: The test context object * @test: The test context object
...@@ -66,34 +33,7 @@ static void kunit_action_platform_device_del(void *ptr) ...@@ -66,34 +33,7 @@ static void kunit_action_platform_device_del(void *ptr)
*/ */
struct device *drm_kunit_helper_alloc_device(struct kunit *test) struct device *drm_kunit_helper_alloc_device(struct kunit *test)
{ {
struct platform_device *pdev; return kunit_device_register(test, KUNIT_DEVICE_NAME);
int ret;
ret = platform_driver_register(&fake_platform_driver);
KUNIT_ASSERT_EQ(test, ret, 0);
ret = kunit_add_action_or_reset(test,
kunit_action_platform_driver_unregister,
&fake_platform_driver);
KUNIT_ASSERT_EQ(test, ret, 0);
pdev = platform_device_alloc(KUNIT_DEVICE_NAME, PLATFORM_DEVID_NONE);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);
ret = kunit_add_action_or_reset(test,
kunit_action_platform_device_put,
pdev);
KUNIT_ASSERT_EQ(test, ret, 0);
ret = platform_device_add(pdev);
KUNIT_ASSERT_EQ(test, ret, 0);
ret = kunit_add_action_or_reset(test,
kunit_action_platform_device_del,
pdev);
KUNIT_ASSERT_EQ(test, ret, 0);
return &pdev->dev;
} }
EXPORT_SYMBOL_GPL(drm_kunit_helper_alloc_device); EXPORT_SYMBOL_GPL(drm_kunit_helper_alloc_device);
...@@ -106,19 +46,7 @@ EXPORT_SYMBOL_GPL(drm_kunit_helper_alloc_device); ...@@ -106,19 +46,7 @@ EXPORT_SYMBOL_GPL(drm_kunit_helper_alloc_device);
*/ */
void drm_kunit_helper_free_device(struct kunit *test, struct device *dev) void drm_kunit_helper_free_device(struct kunit *test, struct device *dev)
{ {
struct platform_device *pdev = to_platform_device(dev); kunit_device_unregister(test, dev);
kunit_release_action(test,
kunit_action_platform_device_del,
pdev);
kunit_release_action(test,
kunit_action_platform_device_put,
pdev);
kunit_release_action(test,
kunit_action_platform_driver_unregister,
&fake_platform_driver);
} }
EXPORT_SYMBOL_GPL(drm_kunit_helper_free_device); EXPORT_SYMBOL_GPL(drm_kunit_helper_free_device);
......
...@@ -153,12 +153,9 @@ static int __build_mock(struct kunit *test, struct drm_device *drm, ...@@ -153,12 +153,9 @@ static int __build_mock(struct kunit *test, struct drm_device *drm,
return 0; return 0;
} }
static void kunit_action_drm_dev_unregister(void *ptr) KUNIT_DEFINE_ACTION_WRAPPER(kunit_action_drm_dev_unregister,
{ drm_dev_unregister,
struct drm_device *drm = ptr; struct drm_device *);
drm_dev_unregister(drm);
}
static struct vc4_dev *__mock_device(struct kunit *test, bool is_vc5) static struct vc4_dev *__mock_device(struct kunit *test, bool is_vc5)
{ {
......
...@@ -370,7 +370,8 @@ ...@@ -370,7 +370,8 @@
BRANCH_PROFILE() \ BRANCH_PROFILE() \
TRACE_PRINTKS() \ TRACE_PRINTKS() \
BPF_RAW_TP() \ BPF_RAW_TP() \
TRACEPOINT_STR() TRACEPOINT_STR() \
KUNIT_TABLE()
/* /*
* Data section helpers * Data section helpers
...@@ -700,7 +701,7 @@ ...@@ -700,7 +701,7 @@
EARLYCON_TABLE() \ EARLYCON_TABLE() \
LSM_TABLE() \ LSM_TABLE() \
EARLY_LSM_TABLE() \ EARLY_LSM_TABLE() \
KUNIT_TABLE() KUNIT_INIT_TABLE()
#define INIT_TEXT \ #define INIT_TEXT \
*(.init.text .init.text.*) \ *(.init.text .init.text.*) \
...@@ -926,6 +927,12 @@ ...@@ -926,6 +927,12 @@
. = ALIGN(8); \ . = ALIGN(8); \
BOUNDED_SECTION_POST_LABEL(.kunit_test_suites, __kunit_suites, _start, _end) BOUNDED_SECTION_POST_LABEL(.kunit_test_suites, __kunit_suites, _start, _end)
/* Alignment must be consistent with (kunit_suite *) in include/kunit/test.h */
#define KUNIT_INIT_TABLE() \
. = ALIGN(8); \
BOUNDED_SECTION_POST_LABEL(.kunit_init_test_suites, \
__kunit_init_suites, _start, _end)
#ifdef CONFIG_BLK_DEV_INITRD #ifdef CONFIG_BLK_DEV_INITRD
#define INIT_RAM_FS \ #define INIT_RAM_FS \
. = ALIGN(4); \ . = ALIGN(4); \
......
/* SPDX-License-Identifier: GPL-2.0 */
/*
* KUnit basic device implementation
*
* Helpers for creating and managing fake devices for KUnit tests.
*
* Copyright (C) 2023, Google LLC.
* Author: David Gow <davidgow@google.com>
*/
#ifndef _KUNIT_DEVICE_H
#define _KUNIT_DEVICE_H
#if IS_ENABLED(CONFIG_KUNIT)
#include <kunit/test.h>
struct device;
struct device_driver;
/**
* kunit_driver_create() - Create a struct device_driver attached to the kunit_bus
* @test: The test context object.
* @name: The name to give the created driver.
*
* Creates a struct device_driver attached to the kunit_bus, with the name @name.
* This driver will automatically be cleaned up on test exit.
*
* Return: a stub struct device_driver, managed by KUnit, with the name @name.
*/
struct device_driver *kunit_driver_create(struct kunit *test, const char *name);
/**
* kunit_device_register() - Create a struct device for use in KUnit tests
* @test: The test context object.
* @name: The name to give the created device.
*
* Creates a struct kunit_device (which is a struct device) with the given name,
* and a corresponding driver. The device and driver will be cleaned up on test
* exit, or when kunit_device_unregister is called. See also
* kunit_device_register_with_driver, if you wish to provide your own
* struct device_driver.
*
* Return: a pointer to a struct device which will be cleaned up when the test
* exits, or an error pointer if the device could not be allocated or registered.
*/
struct device *kunit_device_register(struct kunit *test, const char *name);
/**
* kunit_device_register_with_driver() - Create a struct device for use in KUnit tests
* @test: The test context object.
* @name: The name to give the created device.
* @drv: The struct device_driver to associate with the device.
*
* Creates a struct kunit_device (which is a struct device) with the given
* name, and driver. The device will be cleaned up on test exit, or when
* kunit_device_unregister is called. See also kunit_device_register, if you
* wish KUnit to create and manage a driver for you.
*
* Return: a pointer to a struct device which will be cleaned up when the test
* exits, or an error pointer if the device could not be allocated or registered.
*/
struct device *kunit_device_register_with_driver(struct kunit *test,
const char *name,
const struct device_driver *drv);
/**
* kunit_device_unregister() - Unregister a KUnit-managed device
* @test: The test context object which created the device
* @dev: The device.
*
* Unregisters and destroys a struct device which was created with
* kunit_device_register or kunit_device_register_with_driver. If KUnit created
* a driver, cleans it up as well.
*/
void kunit_device_unregister(struct kunit *test, struct device *dev);
#endif
#endif
...@@ -390,6 +390,27 @@ void kunit_remove_resource(struct kunit *test, struct kunit_resource *res); ...@@ -390,6 +390,27 @@ void kunit_remove_resource(struct kunit *test, struct kunit_resource *res);
/* A 'deferred action' function to be used with kunit_add_action. */ /* A 'deferred action' function to be used with kunit_add_action. */
typedef void (kunit_action_t)(void *); typedef void (kunit_action_t)(void *);
/**
* KUNIT_DEFINE_ACTION_WRAPPER() - Wrap a function for use as a deferred action.
*
* @wrapper: The name of the new wrapper function define.
* @orig: The original function to wrap.
* @arg_type: The type of the argument accepted by @orig.
*
* Defines a wrapper for a function which accepts a single, pointer-sized
* argument. This wrapper can then be passed to kunit_add_action() and
* similar. This should be used in preference to casting a function
* directly to kunit_action_t, as casting function pointers will break
* control flow integrity (CFI), leading to crashes.
*/
#define KUNIT_DEFINE_ACTION_WRAPPER(wrapper, orig, arg_type) \
static void wrapper(void *in) \
{ \
arg_type arg = (arg_type)in; \
orig(arg); \
}
/** /**
* kunit_add_action() - Call a function when the test ends. * kunit_add_action() - Call a function when the test ends.
* @test: Test case to associate the action with. * @test: Test case to associate the action with.
......
...@@ -93,7 +93,7 @@ void __kunit_activate_static_stub(struct kunit *test, ...@@ -93,7 +93,7 @@ void __kunit_activate_static_stub(struct kunit *test,
* The redirection can be disabled again with kunit_deactivate_static_stub(). * The redirection can be disabled again with kunit_deactivate_static_stub().
*/ */
#define kunit_activate_static_stub(test, real_fn_addr, replacement_addr) do { \ #define kunit_activate_static_stub(test, real_fn_addr, replacement_addr) do { \
typecheck_fn(typeof(&real_fn_addr), replacement_addr); \ typecheck_fn(typeof(&replacement_addr), real_fn_addr); \
__kunit_activate_static_stub(test, real_fn_addr, replacement_addr); \ __kunit_activate_static_stub(test, real_fn_addr, replacement_addr); \
} while (0) } while (0)
......
...@@ -253,6 +253,7 @@ struct kunit_suite { ...@@ -253,6 +253,7 @@ struct kunit_suite {
struct dentry *debugfs; struct dentry *debugfs;
struct string_stream *log; struct string_stream *log;
int suite_init_err; int suite_init_err;
bool is_init;
}; };
/* Stores an array of suites, end points one past the end */ /* Stores an array of suites, end points one past the end */
...@@ -337,6 +338,9 @@ void __kunit_test_suites_exit(struct kunit_suite **suites, int num_suites); ...@@ -337,6 +338,9 @@ void __kunit_test_suites_exit(struct kunit_suite **suites, int num_suites);
void kunit_exec_run_tests(struct kunit_suite_set *suite_set, bool builtin); void kunit_exec_run_tests(struct kunit_suite_set *suite_set, bool builtin);
void kunit_exec_list_tests(struct kunit_suite_set *suite_set, bool include_attr); void kunit_exec_list_tests(struct kunit_suite_set *suite_set, bool include_attr);
struct kunit_suite_set kunit_merge_suite_sets(struct kunit_suite_set init_suite_set,
struct kunit_suite_set suite_set);
#if IS_BUILTIN(CONFIG_KUNIT) #if IS_BUILTIN(CONFIG_KUNIT)
int kunit_run_all_tests(void); int kunit_run_all_tests(void);
#else #else
...@@ -371,6 +375,11 @@ static inline int kunit_run_all_tests(void) ...@@ -371,6 +375,11 @@ static inline int kunit_run_all_tests(void)
#define kunit_test_suite(suite) kunit_test_suites(&suite) #define kunit_test_suite(suite) kunit_test_suites(&suite)
#define __kunit_init_test_suites(unique_array, ...) \
static struct kunit_suite *unique_array[] \
__aligned(sizeof(struct kunit_suite *)) \
__used __section(".kunit_init_test_suites") = { __VA_ARGS__ }
/** /**
* kunit_test_init_section_suites() - used to register one or more &struct * kunit_test_init_section_suites() - used to register one or more &struct
* kunit_suite containing init functions or * kunit_suite containing init functions or
...@@ -378,21 +387,21 @@ static inline int kunit_run_all_tests(void) ...@@ -378,21 +387,21 @@ static inline int kunit_run_all_tests(void)
* *
* @__suites: a statically allocated list of &struct kunit_suite. * @__suites: a statically allocated list of &struct kunit_suite.
* *
* This functions identically as kunit_test_suites() except that it suppresses * This functions similar to kunit_test_suites() except that it compiles the
* modpost warnings for referencing functions marked __init or data marked * list of suites during init phase.
* __initdata; this is OK because currently KUnit only runs tests upon boot *
* during the init phase or upon loading a module during the init phase. * This macro also suffixes the array and suite declarations it makes with
* _probe; so that modpost suppresses warnings about referencing init data
* for symbols named in this manner.
* *
* NOTE TO KUNIT DEVS: If we ever allow KUnit tests to be run after boot, these * Note: these init tests are not able to be run after boot so there is no
* tests must be excluded. * "run" debugfs file generated for these tests.
* *
* The only thing this macro does that's different from kunit_test_suites is * Also, do not mark the suite or test case structs with __initdata because
* that it suffixes the array and suite declarations it makes with _probe; * they will be used after the init phase with debugfs.
* modpost suppresses warnings about referencing init data for symbols named in
* this manner.
*/ */
#define kunit_test_init_section_suites(__suites...) \ #define kunit_test_init_section_suites(__suites...) \
__kunit_test_suites(CONCATENATE(__UNIQUE_ID(array), _probe), \ __kunit_init_test_suites(CONCATENATE(__UNIQUE_ID(array), _probe), \
##__suites) ##__suites)
#define kunit_test_init_section_suite(suite) \ #define kunit_test_init_section_suite(suite) \
...@@ -749,7 +758,7 @@ do { \ ...@@ -749,7 +758,7 @@ do { \
.right_text = #right, \ .right_text = #right, \
}; \ }; \
\ \
if (likely(strcmp(__left, __right) op 0)) \ if (likely((__left) && (__right) && (strcmp(__left, __right) op 0))) \
break; \ break; \
\ \
\ \
......
...@@ -540,6 +540,8 @@ struct module { ...@@ -540,6 +540,8 @@ struct module {
struct static_call_site *static_call_sites; struct static_call_site *static_call_sites;
#endif #endif
#if IS_ENABLED(CONFIG_KUNIT) #if IS_ENABLED(CONFIG_KUNIT)
int num_kunit_init_suites;
struct kunit_suite **kunit_init_suites;
int num_kunit_suites; int num_kunit_suites;
struct kunit_suite **kunit_suites; struct kunit_suite **kunit_suites;
#endif #endif
......
...@@ -2199,6 +2199,9 @@ static int find_module_sections(struct module *mod, struct load_info *info) ...@@ -2199,6 +2199,9 @@ static int find_module_sections(struct module *mod, struct load_info *info)
mod->kunit_suites = section_objs(info, ".kunit_test_suites", mod->kunit_suites = section_objs(info, ".kunit_test_suites",
sizeof(*mod->kunit_suites), sizeof(*mod->kunit_suites),
&mod->num_kunit_suites); &mod->num_kunit_suites);
mod->kunit_init_suites = section_objs(info, ".kunit_init_test_suites",
sizeof(*mod->kunit_init_suites),
&mod->num_kunit_init_suites);
#endif #endif
mod->extable = section_objs(info, "__ex_table", mod->extable = section_objs(info, "__ex_table",
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <kunit/device.h>
#include <kunit/test.h> #include <kunit/test.h>
#include <linux/device.h> #include <linux/device.h>
#include <linux/slab.h> #include <linux/slab.h>
...@@ -269,7 +270,7 @@ DEFINE_ALLOC_SIZE_TEST_PAIR(kvmalloc) ...@@ -269,7 +270,7 @@ DEFINE_ALLOC_SIZE_TEST_PAIR(kvmalloc)
size_t len; \ size_t len; \
\ \
/* Create dummy device for devm_kmalloc()-family tests. */ \ /* Create dummy device for devm_kmalloc()-family tests. */ \
dev = root_device_register(dev_name); \ dev = kunit_device_register(test, dev_name); \
KUNIT_ASSERT_FALSE_MSG(test, IS_ERR(dev), \ KUNIT_ASSERT_FALSE_MSG(test, IS_ERR(dev), \
"Cannot register test device\n"); \ "Cannot register test device\n"); \
\ \
...@@ -303,7 +304,7 @@ DEFINE_ALLOC_SIZE_TEST_PAIR(kvmalloc) ...@@ -303,7 +304,7 @@ DEFINE_ALLOC_SIZE_TEST_PAIR(kvmalloc)
checker(len, devm_kmemdup(dev, "Ohai", len, gfp), \ checker(len, devm_kmemdup(dev, "Ohai", len, gfp), \
devm_kfree(dev, p)); \ devm_kfree(dev, p)); \
\ \
device_unregister(dev); \ kunit_device_unregister(test, dev); \
} while (0) } while (0)
DEFINE_ALLOC_SIZE_TEST_PAIR(devm_kmalloc) DEFINE_ALLOC_SIZE_TEST_PAIR(devm_kmalloc)
......
...@@ -7,7 +7,8 @@ kunit-objs += test.o \ ...@@ -7,7 +7,8 @@ kunit-objs += test.o \
assert.o \ assert.o \
try-catch.o \ try-catch.o \
executor.o \ executor.o \
attributes.o attributes.o \
device.o
ifeq ($(CONFIG_KUNIT_DEBUGFS),y) ifeq ($(CONFIG_KUNIT_DEBUGFS),y)
kunit-objs += debugfs.o kunit-objs += debugfs.o
......
...@@ -58,6 +58,16 @@ static const char *attr_enum_to_string(void *attr, const char * const str_list[] ...@@ -58,6 +58,16 @@ static const char *attr_enum_to_string(void *attr, const char * const str_list[]
return str_list[val]; return str_list[val];
} }
static const char *attr_bool_to_string(void *attr, bool *to_free)
{
bool val = (bool)attr;
*to_free = false;
if (val)
return "true";
return "false";
}
static const char *attr_speed_to_string(void *attr, bool *to_free) static const char *attr_speed_to_string(void *attr, bool *to_free)
{ {
return attr_enum_to_string(attr, speed_str_list, to_free); return attr_enum_to_string(attr, speed_str_list, to_free);
...@@ -166,6 +176,37 @@ static int attr_string_filter(void *attr, const char *input, int *err) ...@@ -166,6 +176,37 @@ static int attr_string_filter(void *attr, const char *input, int *err)
return false; return false;
} }
static int attr_bool_filter(void *attr, const char *input, int *err)
{
int i, input_int = -1;
long val = (long)attr;
const char *input_str = NULL;
for (i = 0; input[i]; i++) {
if (!strchr(op_list, input[i])) {
input_str = input + i;
break;
}
}
if (!input_str) {
*err = -EINVAL;
pr_err("kunit executor: filter value not found: %s\n", input);
return false;
}
if (!strcmp(input_str, "true"))
input_int = (int)true;
else if (!strcmp(input_str, "false"))
input_int = (int)false;
else {
*err = -EINVAL;
pr_err("kunit executor: invalid filter input: %s\n", input);
return false;
}
return int_filter(val, input, input_int, err);
}
/* Get Attribute Methods */ /* Get Attribute Methods */
...@@ -194,6 +235,17 @@ static void *attr_module_get(void *test_or_suite, bool is_test) ...@@ -194,6 +235,17 @@ static void *attr_module_get(void *test_or_suite, bool is_test)
return (void *) ""; return (void *) "";
} }
static void *attr_is_init_get(void *test_or_suite, bool is_test)
{
struct kunit_suite *suite = is_test ? NULL : test_or_suite;
struct kunit_case *test = is_test ? test_or_suite : NULL;
if (test)
return ((void *) NULL);
else
return ((void *) suite->is_init);
}
/* List of all Test Attributes */ /* List of all Test Attributes */
static struct kunit_attr kunit_attr_list[] = { static struct kunit_attr kunit_attr_list[] = {
...@@ -212,6 +264,14 @@ static struct kunit_attr kunit_attr_list[] = { ...@@ -212,6 +264,14 @@ static struct kunit_attr kunit_attr_list[] = {
.filter = attr_string_filter, .filter = attr_string_filter,
.attr_default = (void *)"", .attr_default = (void *)"",
.print = PRINT_SUITE, .print = PRINT_SUITE,
},
{
.name = "is_init",
.get_attr = attr_is_init_get,
.to_string = attr_bool_to_string,
.filter = attr_bool_filter,
.attr_default = (void *)false,
.print = PRINT_SUITE,
} }
}; };
......
...@@ -8,12 +8,14 @@ ...@@ -8,12 +8,14 @@
#include <linux/module.h> #include <linux/module.h>
#include <kunit/test.h> #include <kunit/test.h>
#include <kunit/test-bug.h>
#include "string-stream.h" #include "string-stream.h"
#include "debugfs.h" #include "debugfs.h"
#define KUNIT_DEBUGFS_ROOT "kunit" #define KUNIT_DEBUGFS_ROOT "kunit"
#define KUNIT_DEBUGFS_RESULTS "results" #define KUNIT_DEBUGFS_RESULTS "results"
#define KUNIT_DEBUGFS_RUN "run"
/* /*
* Create a debugfs representation of test suites: * Create a debugfs representation of test suites:
...@@ -21,6 +23,8 @@ ...@@ -21,6 +23,8 @@
* Path Semantics * Path Semantics
* /sys/kernel/debug/kunit/<testsuite>/results Show results of last run for * /sys/kernel/debug/kunit/<testsuite>/results Show results of last run for
* testsuite * testsuite
* /sys/kernel/debug/kunit/<testsuite>/run Write to this file to trigger
* testsuite to run
* *
*/ */
...@@ -60,12 +64,14 @@ static void debugfs_print_result(struct seq_file *seq, struct string_stream *log ...@@ -60,12 +64,14 @@ static void debugfs_print_result(struct seq_file *seq, struct string_stream *log
static int debugfs_print_results(struct seq_file *seq, void *v) static int debugfs_print_results(struct seq_file *seq, void *v)
{ {
struct kunit_suite *suite = (struct kunit_suite *)seq->private; struct kunit_suite *suite = (struct kunit_suite *)seq->private;
enum kunit_status success = kunit_suite_has_succeeded(suite); enum kunit_status success;
struct kunit_case *test_case; struct kunit_case *test_case;
if (!suite) if (!suite)
return 0; return 0;
success = kunit_suite_has_succeeded(suite);
/* Print KTAP header so the debugfs log can be parsed as valid KTAP. */ /* Print KTAP header so the debugfs log can be parsed as valid KTAP. */
seq_puts(seq, "KTAP version 1\n"); seq_puts(seq, "KTAP version 1\n");
seq_puts(seq, "1..1\n"); seq_puts(seq, "1..1\n");
...@@ -99,6 +105,51 @@ static int debugfs_results_open(struct inode *inode, struct file *file) ...@@ -99,6 +105,51 @@ static int debugfs_results_open(struct inode *inode, struct file *file)
return single_open(file, debugfs_print_results, suite); return single_open(file, debugfs_print_results, suite);
} }
/*
* Print a usage message to the debugfs "run" file
* (/sys/kernel/debug/kunit/<testsuite>/run) if opened.
*/
static int debugfs_print_run(struct seq_file *seq, void *v)
{
struct kunit_suite *suite = (struct kunit_suite *)seq->private;
seq_puts(seq, "Write to this file to trigger the test suite to run.\n");
seq_printf(seq, "usage: echo \"any string\" > /sys/kernel/debugfs/kunit/%s/run\n",
suite->name);
return 0;
}
/*
* The debugfs "run" file (/sys/kernel/debug/kunit/<testsuite>/run)
* contains no information. Write to the file to trigger the test suite
* to run.
*/
static int debugfs_run_open(struct inode *inode, struct file *file)
{
struct kunit_suite *suite;
suite = (struct kunit_suite *)inode->i_private;
return single_open(file, debugfs_print_run, suite);
}
/*
* Trigger a test suite to run by writing to the suite's "run" debugfs
* file found at: /sys/kernel/debug/kunit/<testsuite>/run
*
* Note: what is written to this file will not be saved.
*/
static ssize_t debugfs_run(struct file *file,
const char __user *buf, size_t count, loff_t *ppos)
{
struct inode *f_inode = file->f_inode;
struct kunit_suite *suite = (struct kunit_suite *) f_inode->i_private;
__kunit_test_suites_init(&suite, 1);
return count;
}
static const struct file_operations debugfs_results_fops = { static const struct file_operations debugfs_results_fops = {
.open = debugfs_results_open, .open = debugfs_results_open,
.read = seq_read, .read = seq_read,
...@@ -106,17 +157,43 @@ static const struct file_operations debugfs_results_fops = { ...@@ -106,17 +157,43 @@ static const struct file_operations debugfs_results_fops = {
.release = debugfs_release, .release = debugfs_release,
}; };
static const struct file_operations debugfs_run_fops = {
.open = debugfs_run_open,
.read = seq_read,
.write = debugfs_run,
.llseek = seq_lseek,
.release = debugfs_release,
};
void kunit_debugfs_create_suite(struct kunit_suite *suite) void kunit_debugfs_create_suite(struct kunit_suite *suite)
{ {
struct kunit_case *test_case; struct kunit_case *test_case;
struct string_stream *stream;
/* If suite log already allocated, do not create new debugfs files. */
if (suite->log)
return;
/* Allocate logs before creating debugfs representation. */ /*
suite->log = alloc_string_stream(GFP_KERNEL); * Allocate logs before creating debugfs representation.
string_stream_set_append_newlines(suite->log, true); * The suite->log and test_case->log pointer are expected to be NULL
* if there isn't a log, so only set it if the log stream was created
* successfully.
*/
stream = alloc_string_stream(GFP_KERNEL);
if (IS_ERR_OR_NULL(stream))
return;
string_stream_set_append_newlines(stream, true);
suite->log = stream;
kunit_suite_for_each_test_case(suite, test_case) { kunit_suite_for_each_test_case(suite, test_case) {
test_case->log = alloc_string_stream(GFP_KERNEL); stream = alloc_string_stream(GFP_KERNEL);
string_stream_set_append_newlines(test_case->log, true); if (IS_ERR_OR_NULL(stream))
goto err;
string_stream_set_append_newlines(stream, true);
test_case->log = stream;
} }
suite->debugfs = debugfs_create_dir(suite->name, debugfs_rootdir); suite->debugfs = debugfs_create_dir(suite->name, debugfs_rootdir);
...@@ -124,6 +201,19 @@ void kunit_debugfs_create_suite(struct kunit_suite *suite) ...@@ -124,6 +201,19 @@ void kunit_debugfs_create_suite(struct kunit_suite *suite)
debugfs_create_file(KUNIT_DEBUGFS_RESULTS, S_IFREG | 0444, debugfs_create_file(KUNIT_DEBUGFS_RESULTS, S_IFREG | 0444,
suite->debugfs, suite->debugfs,
suite, &debugfs_results_fops); suite, &debugfs_results_fops);
/* Do not create file to re-run test if test runs on init */
if (!suite->is_init) {
debugfs_create_file(KUNIT_DEBUGFS_RUN, S_IFREG | 0644,
suite->debugfs,
suite, &debugfs_run_fops);
}
return;
err:
string_stream_destroy(suite->log);
kunit_suite_for_each_test_case(suite, test_case)
string_stream_destroy(test_case->log);
} }
void kunit_debugfs_destroy_suite(struct kunit_suite *suite) void kunit_debugfs_destroy_suite(struct kunit_suite *suite)
......
/* SPDX-License-Identifier: GPL-2.0 */
/*
* KUnit internal header for device helpers
*
* Header for KUnit-internal driver / bus management.
*
* Copyright (C) 2023, Google LLC.
* Author: David Gow <davidgow@google.com>
*/
#ifndef _KUNIT_DEVICE_IMPL_H
#define _KUNIT_DEVICE_IMPL_H
// For internal use only -- registers the kunit_bus.
int kunit_bus_init(void);
#endif //_KUNIT_DEVICE_IMPL_H
// SPDX-License-Identifier: GPL-2.0
/*
* KUnit-managed device implementation
*
* Implementation of struct kunit_device helpers for fake devices whose
* lifecycle is managed by KUnit.
*
* Copyright (C) 2023, Google LLC.
* Author: David Gow <davidgow@google.com>
*/
#include <linux/device.h>
#include <kunit/test.h>
#include <kunit/device.h>
#include <kunit/resource.h>
#include "device-impl.h"
/* Wrappers for use with kunit_add_action() */
KUNIT_DEFINE_ACTION_WRAPPER(device_unregister_wrapper, device_unregister, struct device *);
KUNIT_DEFINE_ACTION_WRAPPER(driver_unregister_wrapper, driver_unregister, struct device_driver *);
/* The root device for the KUnit bus, parent of all kunit_devices. */
static struct device *kunit_bus_device;
/* A device owned by a KUnit test. */
struct kunit_device {
struct device dev;
/* The KUnit test which owns this device. */
struct kunit *owner;
/* If the driver is managed by KUnit and unique to this device. */
const struct device_driver *driver;
};
#define to_kunit_device(d) container_of_const(d, struct kunit_device, dev)
static struct bus_type kunit_bus_type = {
.name = "kunit",
};
/* Register the 'kunit_bus' used for fake devices. */
int kunit_bus_init(void)
{
int error;
kunit_bus_device = root_device_register("kunit");
if (!kunit_bus_device)
return -ENOMEM;
error = bus_register(&kunit_bus_type);
if (error)
bus_unregister(&kunit_bus_type);
return error;
}
/* Release a 'fake' KUnit device. */
static void kunit_device_release(struct device *d)
{
kfree(to_kunit_device(d));
}
/*
* Create and register a KUnit-managed struct device_driver on the kunit_bus.
* Returns an error pointer on failure.
*/
struct device_driver *kunit_driver_create(struct kunit *test, const char *name)
{
struct device_driver *driver;
int err = -ENOMEM;
driver = kunit_kzalloc(test, sizeof(*driver), GFP_KERNEL);
if (!driver)
return ERR_PTR(err);
driver->name = name;
driver->bus = &kunit_bus_type;
driver->owner = THIS_MODULE;
err = driver_register(driver);
if (err) {
kunit_kfree(test, driver);
return ERR_PTR(err);
}
kunit_add_action(test, driver_unregister_wrapper, driver);
return driver;
}
EXPORT_SYMBOL_GPL(kunit_driver_create);
/* Helper which creates a kunit_device, attaches it to the kunit_bus*/
static struct kunit_device *kunit_device_register_internal(struct kunit *test,
const char *name,
const struct device_driver *drv)
{
struct kunit_device *kunit_dev;
int err = -ENOMEM;
kunit_dev = kzalloc(sizeof(*kunit_dev), GFP_KERNEL);
if (!kunit_dev)
return ERR_PTR(err);
kunit_dev->owner = test;
err = dev_set_name(&kunit_dev->dev, "%s.%s", test->name, name);
if (err) {
kfree(kunit_dev);
return ERR_PTR(err);
}
kunit_dev->dev.release = kunit_device_release;
kunit_dev->dev.bus = &kunit_bus_type;
kunit_dev->dev.parent = kunit_bus_device;
err = device_register(&kunit_dev->dev);
if (err) {
put_device(&kunit_dev->dev);
return ERR_PTR(err);
}
kunit_add_action(test, device_unregister_wrapper, &kunit_dev->dev);
return kunit_dev;
}
/*
* Create and register a new KUnit-managed device, using the user-supplied device_driver.
* On failure, returns an error pointer.
*/
struct device *kunit_device_register_with_driver(struct kunit *test,
const char *name,
const struct device_driver *drv)
{
struct kunit_device *kunit_dev = kunit_device_register_internal(test, name, drv);
if (IS_ERR_OR_NULL(kunit_dev))
return ERR_CAST(kunit_dev);
return &kunit_dev->dev;
}
EXPORT_SYMBOL_GPL(kunit_device_register_with_driver);
/*
* Create and register a new KUnit-managed device, including a matching device_driver.
* On failure, returns an error pointer.
*/
struct device *kunit_device_register(struct kunit *test, const char *name)
{
struct device_driver *drv;
struct kunit_device *dev;
drv = kunit_driver_create(test, name);
if (IS_ERR(drv))
return ERR_CAST(drv);
dev = kunit_device_register_internal(test, name, drv);
if (IS_ERR(dev)) {
kunit_release_action(test, driver_unregister_wrapper, (void *)drv);
return ERR_CAST(dev);
}
/* Request the driver be freed. */
dev->driver = drv;
return &dev->dev;
}
EXPORT_SYMBOL_GPL(kunit_device_register);
/* Unregisters a KUnit-managed device early (including the driver, if automatically created). */
void kunit_device_unregister(struct kunit *test, struct device *dev)
{
const struct device_driver *driver = to_kunit_device(dev)->driver;
kunit_release_action(test, device_unregister_wrapper, dev);
if (driver)
kunit_release_action(test, driver_unregister_wrapper, (void *)driver);
}
EXPORT_SYMBOL_GPL(kunit_device_unregister);
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
*/ */
extern struct kunit_suite * const __kunit_suites_start[]; extern struct kunit_suite * const __kunit_suites_start[];
extern struct kunit_suite * const __kunit_suites_end[]; extern struct kunit_suite * const __kunit_suites_end[];
extern struct kunit_suite * const __kunit_init_suites_start[];
extern struct kunit_suite * const __kunit_init_suites_end[];
static char *action_param; static char *action_param;
...@@ -292,6 +294,37 @@ void kunit_exec_list_tests(struct kunit_suite_set *suite_set, bool include_attr) ...@@ -292,6 +294,37 @@ void kunit_exec_list_tests(struct kunit_suite_set *suite_set, bool include_attr)
} }
} }
struct kunit_suite_set kunit_merge_suite_sets(struct kunit_suite_set init_suite_set,
struct kunit_suite_set suite_set)
{
struct kunit_suite_set total_suite_set = {NULL, NULL};
struct kunit_suite **total_suite_start = NULL;
size_t init_num_suites, num_suites, suite_size;
int i = 0;
init_num_suites = init_suite_set.end - init_suite_set.start;
num_suites = suite_set.end - suite_set.start;
suite_size = sizeof(suite_set.start);
/* Allocate memory for array of all kunit suites */
total_suite_start = kmalloc_array(init_num_suites + num_suites, suite_size, GFP_KERNEL);
if (!total_suite_start)
return total_suite_set;
/* Append and mark init suites and then append all other kunit suites */
memcpy(total_suite_start, init_suite_set.start, init_num_suites * suite_size);
for (i = 0; i < init_num_suites; i++)
total_suite_start[i]->is_init = true;
memcpy(total_suite_start + init_num_suites, suite_set.start, num_suites * suite_size);
/* Set kunit suite set start and end */
total_suite_set.start = total_suite_start;
total_suite_set.end = total_suite_start + (init_num_suites + num_suites);
return total_suite_set;
}
#if IS_BUILTIN(CONFIG_KUNIT) #if IS_BUILTIN(CONFIG_KUNIT)
static char *kunit_shutdown; static char *kunit_shutdown;
...@@ -313,21 +346,41 @@ static void kunit_handle_shutdown(void) ...@@ -313,21 +346,41 @@ static void kunit_handle_shutdown(void)
int kunit_run_all_tests(void) int kunit_run_all_tests(void)
{ {
struct kunit_suite_set suite_set = { struct kunit_suite_set suite_set = {NULL, NULL};
struct kunit_suite_set filtered_suite_set = {NULL, NULL};
struct kunit_suite_set init_suite_set = {
__kunit_init_suites_start, __kunit_init_suites_end,
};
struct kunit_suite_set normal_suite_set = {
__kunit_suites_start, __kunit_suites_end, __kunit_suites_start, __kunit_suites_end,
}; };
size_t init_num_suites = init_suite_set.end - init_suite_set.start;
int err = 0; int err = 0;
if (init_num_suites > 0) {
suite_set = kunit_merge_suite_sets(init_suite_set, normal_suite_set);
if (!suite_set.start)
goto out;
} else
suite_set = normal_suite_set;
if (!kunit_enabled()) { if (!kunit_enabled()) {
pr_info("kunit: disabled\n"); pr_info("kunit: disabled\n");
goto out; goto free_out;
} }
if (filter_glob_param || filter_param) { if (filter_glob_param || filter_param) {
suite_set = kunit_filter_suites(&suite_set, filter_glob_param, filtered_suite_set = kunit_filter_suites(&suite_set, filter_glob_param,
filter_param, filter_action_param, &err); filter_param, filter_action_param, &err);
/* Free original suite set before using filtered suite set */
if (init_num_suites > 0)
kfree(suite_set.start);
suite_set = filtered_suite_set;
if (err) { if (err) {
pr_err("kunit executor: error filtering suites: %d\n", err); pr_err("kunit executor: error filtering suites: %d\n", err);
goto out; goto free_out;
} }
} }
...@@ -340,9 +393,12 @@ int kunit_run_all_tests(void) ...@@ -340,9 +393,12 @@ int kunit_run_all_tests(void)
else else
pr_err("kunit executor: unknown action '%s'\n", action_param); pr_err("kunit executor: unknown action '%s'\n", action_param);
if (filter_glob_param || filter_param) { /* a copy was made of each suite */ free_out:
if (filter_glob_param || filter_param)
kunit_free_suite_set(suite_set); kunit_free_suite_set(suite_set);
} else if (init_num_suites > 0)
/* Don't use kunit_free_suite_set because suites aren't individually allocated */
kfree(suite_set.start);
out: out:
kunit_handle_shutdown(); kunit_handle_shutdown();
......
...@@ -168,6 +168,16 @@ static int subtract_one(int i) ...@@ -168,6 +168,16 @@ static int subtract_one(int i)
return i - 1; return i - 1;
} }
/*
* If the function to be replaced is static within a module it is
* useful to export a pointer to that function instead of having
* to change the static function to a non-static exported function.
*
* This pointer simulates a module exporting a pointer to a static
* function.
*/
static int (* const add_one_fn_ptr)(int i) = add_one;
/* /*
* This test shows the use of static stubs. * This test shows the use of static stubs.
*/ */
...@@ -187,6 +197,30 @@ static void example_static_stub_test(struct kunit *test) ...@@ -187,6 +197,30 @@ static void example_static_stub_test(struct kunit *test)
KUNIT_EXPECT_EQ(test, add_one(1), 2); KUNIT_EXPECT_EQ(test, add_one(1), 2);
} }
/*
* This test shows the use of static stubs when the function being
* replaced is provided as a pointer-to-function instead of the
* actual function. This is useful for providing access to static
* functions in a module by exporting a pointer to that function
* instead of having to change the static function to a non-static
* exported function.
*/
static void example_static_stub_using_fn_ptr_test(struct kunit *test)
{
/* By default, function is not stubbed. */
KUNIT_EXPECT_EQ(test, add_one(1), 2);
/* Replace add_one() with subtract_one(). */
kunit_activate_static_stub(test, add_one_fn_ptr, subtract_one);
/* add_one() is now replaced. */
KUNIT_EXPECT_EQ(test, add_one(1), 0);
/* Return add_one() to normal. */
kunit_deactivate_static_stub(test, add_one_fn_ptr);
KUNIT_EXPECT_EQ(test, add_one(1), 2);
}
static const struct example_param { static const struct example_param {
int value; int value;
} example_params_array[] = { } example_params_array[] = {
...@@ -221,6 +255,20 @@ static void example_params_test(struct kunit *test) ...@@ -221,6 +255,20 @@ static void example_params_test(struct kunit *test)
KUNIT_EXPECT_EQ(test, param->value % param->value, 0); KUNIT_EXPECT_EQ(test, param->value % param->value, 0);
} }
/*
* This test shows the use of test->priv.
*/
static void example_priv_test(struct kunit *test)
{
/* unless setup in suite->init(), test->priv is NULL */
KUNIT_ASSERT_NULL(test, test->priv);
/* but can be used to pass arbitrary data to other functions */
test->priv = kunit_kzalloc(test, 1, GFP_KERNEL);
KUNIT_EXPECT_NOT_NULL(test, test->priv);
KUNIT_ASSERT_PTR_EQ(test, test->priv, kunit_get_current_test()->priv);
}
/* /*
* This test should always pass. Can be used to practice filtering attributes. * This test should always pass. Can be used to practice filtering attributes.
*/ */
...@@ -245,6 +293,8 @@ static struct kunit_case example_test_cases[] = { ...@@ -245,6 +293,8 @@ static struct kunit_case example_test_cases[] = {
KUNIT_CASE(example_mark_skipped_test), KUNIT_CASE(example_mark_skipped_test),
KUNIT_CASE(example_all_expect_macros_test), KUNIT_CASE(example_all_expect_macros_test),
KUNIT_CASE(example_static_stub_test), KUNIT_CASE(example_static_stub_test),
KUNIT_CASE(example_static_stub_using_fn_ptr_test),
KUNIT_CASE(example_priv_test),
KUNIT_CASE_PARAM(example_params_test, example_gen_params), KUNIT_CASE_PARAM(example_params_test, example_gen_params),
KUNIT_CASE_SLOW(example_slow_test), KUNIT_CASE_SLOW(example_slow_test),
{} {}
...@@ -287,4 +337,41 @@ static struct kunit_suite example_test_suite = { ...@@ -287,4 +337,41 @@ static struct kunit_suite example_test_suite = {
*/ */
kunit_test_suites(&example_test_suite); kunit_test_suites(&example_test_suite);
static int __init init_add(int x, int y)
{
return (x + y);
}
/*
* This test should always pass. Can be used to test init suites.
*/
static void __init example_init_test(struct kunit *test)
{
KUNIT_EXPECT_EQ(test, init_add(1, 1), 2);
}
/*
* The kunit_case struct cannot be marked as __initdata as this will be
* used in debugfs to retrieve results after test has run
*/
static struct kunit_case __refdata example_init_test_cases[] = {
KUNIT_CASE(example_init_test),
{}
};
/*
* The kunit_suite struct cannot be marked as __initdata as this will be
* used in debugfs to retrieve results after test has run
*/
static struct kunit_suite example_init_test_suite = {
.name = "example_init",
.test_cases = example_init_test_cases,
};
/*
* This registers the test suite and marks the suite as using init data
* and/or functions.
*/
kunit_test_init_section_suites(&example_init_test_suite);
MODULE_LICENSE("GPL v2"); MODULE_LICENSE("GPL v2");
...@@ -5,9 +5,13 @@ ...@@ -5,9 +5,13 @@
* Copyright (C) 2019, Google LLC. * Copyright (C) 2019, Google LLC.
* Author: Brendan Higgins <brendanhiggins@google.com> * Author: Brendan Higgins <brendanhiggins@google.com>
*/ */
#include "linux/gfp_types.h"
#include <kunit/test.h> #include <kunit/test.h>
#include <kunit/test-bug.h> #include <kunit/test-bug.h>
#include <linux/device.h>
#include <kunit/device.h>
#include "string-stream.h" #include "string-stream.h"
#include "try-catch-impl.h" #include "try-catch-impl.h"
...@@ -538,10 +542,7 @@ static struct kunit_suite kunit_resource_test_suite = { ...@@ -538,10 +542,7 @@ static struct kunit_suite kunit_resource_test_suite = {
#if IS_BUILTIN(CONFIG_KUNIT_TEST) #if IS_BUILTIN(CONFIG_KUNIT_TEST)
/* This avoids a cast warning if kfree() is passed direct to kunit_add_action(). */ /* This avoids a cast warning if kfree() is passed direct to kunit_add_action(). */
static void kfree_wrapper(void *p) KUNIT_DEFINE_ACTION_WRAPPER(kfree_wrapper, kfree, const void *);
{
kfree(p);
}
static void kunit_log_test(struct kunit *test) static void kunit_log_test(struct kunit *test)
{ {
...@@ -690,6 +691,134 @@ static struct kunit_case kunit_current_test_cases[] = { ...@@ -690,6 +691,134 @@ static struct kunit_case kunit_current_test_cases[] = {
{} {}
}; };
static void test_dev_action(void *priv)
{
*(void **)priv = (void *)1;
}
static void kunit_device_test(struct kunit *test)
{
struct device *test_device;
long action_was_run = 0;
test_device = kunit_device_register(test, "my_device");
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_device);
// Add an action to verify cleanup.
devm_add_action(test_device, test_dev_action, &action_was_run);
KUNIT_EXPECT_EQ(test, action_was_run, 0);
kunit_device_unregister(test, test_device);
KUNIT_EXPECT_EQ(test, action_was_run, 1);
}
static void kunit_device_cleanup_test(struct kunit *test)
{
struct device *test_device;
long action_was_run = 0;
test_device = kunit_device_register(test, "my_device");
KUNIT_ASSERT_NOT_NULL(test, test_device);
/* Add an action to verify cleanup. */
devm_add_action(test_device, test_dev_action, &action_was_run);
KUNIT_EXPECT_EQ(test, action_was_run, 0);
/* Force KUnit to run cleanup early. */
kunit_cleanup(test);
KUNIT_EXPECT_EQ(test, action_was_run, 1);
}
struct driver_test_state {
bool driver_device_probed;
bool driver_device_removed;
long action_was_run;
};
static int driver_probe_hook(struct device *dev)
{
struct kunit *test = kunit_get_current_test();
struct driver_test_state *state = (struct driver_test_state *)test->priv;
state->driver_device_probed = true;
return 0;
}
static int driver_remove_hook(struct device *dev)
{
struct kunit *test = kunit_get_current_test();
struct driver_test_state *state = (struct driver_test_state *)test->priv;
state->driver_device_removed = true;
return 0;
}
static void kunit_device_driver_test(struct kunit *test)
{
struct device_driver *test_driver;
struct device *test_device;
struct driver_test_state *test_state = kunit_kzalloc(test, sizeof(*test_state), GFP_KERNEL);
test->priv = test_state;
test_driver = kunit_driver_create(test, "my_driver");
// This can fail with an error pointer.
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_driver);
test_driver->probe = driver_probe_hook;
test_driver->remove = driver_remove_hook;
test_device = kunit_device_register_with_driver(test, "my_device", test_driver);
// This can fail with an error pointer.
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_device);
// Make sure the probe function was called.
KUNIT_ASSERT_TRUE(test, test_state->driver_device_probed);
// Add an action to verify cleanup.
devm_add_action(test_device, test_dev_action, &test_state->action_was_run);
KUNIT_EXPECT_EQ(test, test_state->action_was_run, 0);
kunit_device_unregister(test, test_device);
test_device = NULL;
// Make sure the remove hook was called.
KUNIT_ASSERT_TRUE(test, test_state->driver_device_removed);
// We're going to test this again.
test_state->driver_device_probed = false;
// The driver should not automatically be destroyed by
// kunit_device_unregister, so we can re-use it.
test_device = kunit_device_register_with_driver(test, "my_device", test_driver);
// This can fail with an error pointer.
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_device);
// Probe was called again.
KUNIT_ASSERT_TRUE(test, test_state->driver_device_probed);
// Everything is automatically freed here.
}
static struct kunit_case kunit_device_test_cases[] = {
KUNIT_CASE(kunit_device_test),
KUNIT_CASE(kunit_device_cleanup_test),
KUNIT_CASE(kunit_device_driver_test),
{}
};
static struct kunit_suite kunit_device_test_suite = {
.name = "kunit_device",
.test_cases = kunit_device_test_cases,
};
static struct kunit_suite kunit_current_test_suite = { static struct kunit_suite kunit_current_test_suite = {
.name = "kunit_current", .name = "kunit_current",
.test_cases = kunit_current_test_cases, .test_cases = kunit_current_test_cases,
...@@ -697,6 +826,6 @@ static struct kunit_suite kunit_current_test_suite = { ...@@ -697,6 +826,6 @@ static struct kunit_suite kunit_current_test_suite = {
kunit_test_suites(&kunit_try_catch_test_suite, &kunit_resource_test_suite, kunit_test_suites(&kunit_try_catch_test_suite, &kunit_resource_test_suite,
&kunit_log_test_suite, &kunit_status_test_suite, &kunit_log_test_suite, &kunit_status_test_suite,
&kunit_current_test_suite); &kunit_current_test_suite, &kunit_device_test_suite);
MODULE_LICENSE("GPL v2"); MODULE_LICENSE("GPL v2");
...@@ -72,7 +72,7 @@ static void string_stream_unmanaged_init_test(struct kunit *test) ...@@ -72,7 +72,7 @@ static void string_stream_unmanaged_init_test(struct kunit *test)
KUNIT_EXPECT_EQ(test, stream->length, 0); KUNIT_EXPECT_EQ(test, stream->length, 0);
KUNIT_EXPECT_TRUE(test, list_empty(&stream->fragments)); KUNIT_EXPECT_TRUE(test, list_empty(&stream->fragments));
KUNIT_EXPECT_EQ(test, stream->gfp, GFP_KERNEL); KUNIT_EXPECT_TRUE(test, (stream->gfp == GFP_KERNEL));
KUNIT_EXPECT_FALSE(test, stream->append_newlines); KUNIT_EXPECT_FALSE(test, stream->append_newlines);
KUNIT_EXPECT_TRUE(test, string_stream_is_empty(stream)); KUNIT_EXPECT_TRUE(test, string_stream_is_empty(stream));
......
...@@ -173,7 +173,7 @@ void string_stream_destroy(struct string_stream *stream) ...@@ -173,7 +173,7 @@ void string_stream_destroy(struct string_stream *stream)
{ {
KUNIT_STATIC_STUB_REDIRECT(string_stream_destroy, stream); KUNIT_STATIC_STUB_REDIRECT(string_stream_destroy, stream);
if (!stream) if (IS_ERR_OR_NULL(stream))
return; return;
string_stream_clear(stream); string_stream_clear(stream);
......
...@@ -13,15 +13,19 @@ ...@@ -13,15 +13,19 @@
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/moduleparam.h> #include <linux/moduleparam.h>
#include <linux/mutex.h>
#include <linux/panic.h> #include <linux/panic.h>
#include <linux/sched/debug.h> #include <linux/sched/debug.h>
#include <linux/sched.h> #include <linux/sched.h>
#include "debugfs.h" #include "debugfs.h"
#include "device-impl.h"
#include "hooks-impl.h" #include "hooks-impl.h"
#include "string-stream.h" #include "string-stream.h"
#include "try-catch-impl.h" #include "try-catch-impl.h"
static DEFINE_MUTEX(kunit_run_lock);
/* /*
* Hook to fail the current test and print an error message to the log. * Hook to fail the current test and print an error message to the log.
*/ */
...@@ -660,6 +664,7 @@ int kunit_run_tests(struct kunit_suite *suite) ...@@ -660,6 +664,7 @@ int kunit_run_tests(struct kunit_suite *suite)
test.param_index++; test.param_index++;
test.status = KUNIT_SUCCESS; test.status = KUNIT_SUCCESS;
test.status_comment[0] = '\0'; test.status_comment[0] = '\0';
test.priv = NULL;
} }
} }
...@@ -692,6 +697,9 @@ static void kunit_init_suite(struct kunit_suite *suite) ...@@ -692,6 +697,9 @@ static void kunit_init_suite(struct kunit_suite *suite)
kunit_debugfs_create_suite(suite); kunit_debugfs_create_suite(suite);
suite->status_comment[0] = '\0'; suite->status_comment[0] = '\0';
suite->suite_init_err = 0; suite->suite_init_err = 0;
if (suite->log)
string_stream_clear(suite->log);
} }
bool kunit_enabled(void) bool kunit_enabled(void)
...@@ -710,6 +718,11 @@ int __kunit_test_suites_init(struct kunit_suite * const * const suites, int num_ ...@@ -710,6 +718,11 @@ int __kunit_test_suites_init(struct kunit_suite * const * const suites, int num_
kunit_suite_counter = 1; kunit_suite_counter = 1;
/* Use mutex lock to guard against running tests concurrently. */
if (mutex_lock_interruptible(&kunit_run_lock)) {
pr_err("kunit: test interrupted\n");
return -EINTR;
}
static_branch_inc(&kunit_running); static_branch_inc(&kunit_running);
for (i = 0; i < num_suites; i++) { for (i = 0; i < num_suites; i++) {
...@@ -718,6 +731,7 @@ int __kunit_test_suites_init(struct kunit_suite * const * const suites, int num_ ...@@ -718,6 +731,7 @@ int __kunit_test_suites_init(struct kunit_suite * const * const suites, int num_
} }
static_branch_dec(&kunit_running); static_branch_dec(&kunit_running);
mutex_unlock(&kunit_run_lock);
return 0; return 0;
} }
EXPORT_SYMBOL_GPL(__kunit_test_suites_init); EXPORT_SYMBOL_GPL(__kunit_test_suites_init);
...@@ -742,28 +756,40 @@ EXPORT_SYMBOL_GPL(__kunit_test_suites_exit); ...@@ -742,28 +756,40 @@ EXPORT_SYMBOL_GPL(__kunit_test_suites_exit);
#ifdef CONFIG_MODULES #ifdef CONFIG_MODULES
static void kunit_module_init(struct module *mod) static void kunit_module_init(struct module *mod)
{ {
struct kunit_suite_set suite_set = { struct kunit_suite_set suite_set, filtered_set;
struct kunit_suite_set normal_suite_set = {
mod->kunit_suites, mod->kunit_suites + mod->num_kunit_suites, mod->kunit_suites, mod->kunit_suites + mod->num_kunit_suites,
}; };
struct kunit_suite_set init_suite_set = {
mod->kunit_init_suites, mod->kunit_init_suites + mod->num_kunit_init_suites,
};
const char *action = kunit_action(); const char *action = kunit_action();
int err = 0; int err = 0;
suite_set = kunit_filter_suites(&suite_set, if (mod->num_kunit_init_suites > 0)
suite_set = kunit_merge_suite_sets(init_suite_set, normal_suite_set);
else
suite_set = normal_suite_set;
filtered_set = kunit_filter_suites(&suite_set,
kunit_filter_glob() ?: "*.*", kunit_filter_glob() ?: "*.*",
kunit_filter(), kunit_filter_action(), kunit_filter(), kunit_filter_action(),
&err); &err);
if (err) if (err)
pr_err("kunit module: error filtering suites: %d\n", err); pr_err("kunit module: error filtering suites: %d\n", err);
mod->kunit_suites = (struct kunit_suite **)suite_set.start; mod->kunit_suites = (struct kunit_suite **)filtered_set.start;
mod->num_kunit_suites = suite_set.end - suite_set.start; mod->num_kunit_suites = filtered_set.end - filtered_set.start;
if (mod->num_kunit_init_suites > 0)
kfree(suite_set.start);
if (!action) if (!action)
kunit_exec_run_tests(&suite_set, false); kunit_exec_run_tests(&filtered_set, false);
else if (!strcmp(action, "list")) else if (!strcmp(action, "list"))
kunit_exec_list_tests(&suite_set, false); kunit_exec_list_tests(&filtered_set, false);
else if (!strcmp(action, "list_attr")) else if (!strcmp(action, "list_attr"))
kunit_exec_list_tests(&suite_set, true); kunit_exec_list_tests(&filtered_set, true);
else else
pr_err("kunit: unknown action '%s'\n", action); pr_err("kunit: unknown action '%s'\n", action);
} }
...@@ -810,6 +836,8 @@ static struct notifier_block kunit_mod_nb = { ...@@ -810,6 +836,8 @@ static struct notifier_block kunit_mod_nb = {
}; };
#endif #endif
KUNIT_DEFINE_ACTION_WRAPPER(kfree_action_wrapper, kfree, const void *)
void *kunit_kmalloc_array(struct kunit *test, size_t n, size_t size, gfp_t gfp) void *kunit_kmalloc_array(struct kunit *test, size_t n, size_t size, gfp_t gfp)
{ {
void *data; void *data;
...@@ -819,7 +847,7 @@ void *kunit_kmalloc_array(struct kunit *test, size_t n, size_t size, gfp_t gfp) ...@@ -819,7 +847,7 @@ void *kunit_kmalloc_array(struct kunit *test, size_t n, size_t size, gfp_t gfp)
if (!data) if (!data)
return NULL; return NULL;
if (kunit_add_action_or_reset(test, (kunit_action_t *)kfree, data) != 0) if (kunit_add_action_or_reset(test, kfree_action_wrapper, data) != 0)
return NULL; return NULL;
return data; return data;
...@@ -831,7 +859,7 @@ void kunit_kfree(struct kunit *test, const void *ptr) ...@@ -831,7 +859,7 @@ void kunit_kfree(struct kunit *test, const void *ptr)
if (!ptr) if (!ptr)
return; return;
kunit_release_action(test, (kunit_action_t *)kfree, (void *)ptr); kunit_release_action(test, kfree_action_wrapper, (void *)ptr);
} }
EXPORT_SYMBOL_GPL(kunit_kfree); EXPORT_SYMBOL_GPL(kunit_kfree);
...@@ -876,6 +904,8 @@ static int __init kunit_init(void) ...@@ -876,6 +904,8 @@ static int __init kunit_init(void)
kunit_install_hooks(); kunit_install_hooks();
kunit_debugfs_init(); kunit_debugfs_init();
kunit_bus_init();
#ifdef CONFIG_MODULES #ifdef CONFIG_MODULES
return register_module_notifier(&kunit_mod_nb); return register_module_notifier(&kunit_mod_nb);
#else #else
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
*/ */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <kunit/device.h>
#include <kunit/test.h> #include <kunit/test.h>
#include <linux/device.h> #include <linux/device.h>
#include <linux/kernel.h> #include <linux/kernel.h>
...@@ -618,7 +619,7 @@ static void overflow_allocation_test(struct kunit *test) ...@@ -618,7 +619,7 @@ static void overflow_allocation_test(struct kunit *test)
} while (0) } while (0)
/* Create dummy device for devm_kmalloc()-family tests. */ /* Create dummy device for devm_kmalloc()-family tests. */
dev = root_device_register(device_name); dev = kunit_device_register(test, device_name);
KUNIT_ASSERT_FALSE_MSG(test, IS_ERR(dev), KUNIT_ASSERT_FALSE_MSG(test, IS_ERR(dev),
"Cannot register test device\n"); "Cannot register test device\n");
...@@ -634,8 +635,6 @@ static void overflow_allocation_test(struct kunit *test) ...@@ -634,8 +635,6 @@ static void overflow_allocation_test(struct kunit *test)
check_allocation_overflow(devm_kmalloc); check_allocation_overflow(devm_kmalloc);
check_allocation_overflow(devm_kzalloc); check_allocation_overflow(devm_kzalloc);
device_unregister(dev);
kunit_info(test, "%d allocation overflow tests finished\n", count); kunit_info(test, "%d allocation overflow tests finished\n", count);
#undef check_allocation_overflow #undef check_allocation_overflow
} }
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include <sound/core.h> #include <sound/core.h>
#include <sound/soc.h> #include <sound/soc.h>
#include <sound/soc-topology.h> #include <sound/soc-topology.h>
#include <kunit/device.h>
#include <kunit/test.h> #include <kunit/test.h>
/* ===== HELPER FUNCTIONS =================================================== */ /* ===== HELPER FUNCTIONS =================================================== */
...@@ -21,26 +22,19 @@ ...@@ -21,26 +22,19 @@
*/ */
static struct device *test_dev; static struct device *test_dev;
static struct device_driver test_drv = {
.name = "sound-soc-topology-test-driver",
};
static int snd_soc_tplg_test_init(struct kunit *test) static int snd_soc_tplg_test_init(struct kunit *test)
{ {
test_dev = root_device_register("sound-soc-topology-test"); test_dev = kunit_device_register(test, "sound-soc-topology-test");
test_dev = get_device(test_dev); test_dev = get_device(test_dev);
if (!test_dev) if (!test_dev)
return -ENODEV; return -ENODEV;
test_dev->driver = &test_drv;
return 0; return 0;
} }
static void snd_soc_tplg_test_exit(struct kunit *test) static void snd_soc_tplg_test_exit(struct kunit *test)
{ {
put_device(test_dev); put_device(test_dev);
root_device_unregister(test_dev);
} }
/* /*
......
...@@ -450,7 +450,7 @@ def parse_diagnostic(lines: LineStream) -> List[str]: ...@@ -450,7 +450,7 @@ def parse_diagnostic(lines: LineStream) -> List[str]:
Log of diagnostic lines Log of diagnostic lines
""" """
log = [] # type: List[str] log = [] # type: List[str]
non_diagnostic_lines = [TEST_RESULT, TEST_HEADER, KTAP_START, TAP_START] non_diagnostic_lines = [TEST_RESULT, TEST_HEADER, KTAP_START, TAP_START, TEST_PLAN]
while lines and not any(re.match(lines.peek()) while lines and not any(re.match(lines.peek())
for re in non_diagnostic_lines): for re in non_diagnostic_lines):
log.append(lines.pop()) log.append(lines.pop())
...@@ -726,6 +726,7 @@ def parse_test(lines: LineStream, expected_num: int, log: List[str], is_subtest: ...@@ -726,6 +726,7 @@ def parse_test(lines: LineStream, expected_num: int, log: List[str], is_subtest:
# test plan # test plan
test.name = "main" test.name = "main"
ktap_line = parse_ktap_header(lines, test) ktap_line = parse_ktap_header(lines, test)
test.log.extend(parse_diagnostic(lines))
parse_test_plan(lines, test) parse_test_plan(lines, test)
parent_test = True parent_test = True
else: else:
...@@ -737,6 +738,7 @@ def parse_test(lines: LineStream, expected_num: int, log: List[str], is_subtest: ...@@ -737,6 +738,7 @@ def parse_test(lines: LineStream, expected_num: int, log: List[str], is_subtest:
if parent_test: if parent_test:
# If KTAP version line and/or subtest header is found, attempt # If KTAP version line and/or subtest header is found, attempt
# to parse test plan and print test header # to parse test plan and print test header
test.log.extend(parse_diagnostic(lines))
parse_test_plan(lines, test) parse_test_plan(lines, test)
print_test_header(test) print_test_header(test)
expected_count = test.expected_count expected_count = test.expected_count
......
...@@ -331,6 +331,22 @@ class KUnitParserTest(unittest.TestCase): ...@@ -331,6 +331,22 @@ class KUnitParserTest(unittest.TestCase):
kunit_parser.parse_run_tests(file.readlines()) kunit_parser.parse_run_tests(file.readlines())
self.print_mock.assert_any_call(StrContains('suite (1 subtest)')) self.print_mock.assert_any_call(StrContains('suite (1 subtest)'))
def test_parse_attributes(self):
ktap_log = test_data_path('test_parse_attributes.log')
with open(ktap_log) as file:
result = kunit_parser.parse_run_tests(file.readlines())
# Test should pass with no errors
self.assertEqual(result.counts, kunit_parser.TestCounts(passed=1, errors=0))
self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
# Ensure suite header is parsed correctly
self.print_mock.assert_any_call(StrContains('suite (1 subtest)'))
# Ensure attributes in correct test log
self.assertContains('# module: example', result.subtests[0].log)
self.assertContains('# test.speed: slow', result.subtests[0].subtests[0].log)
def test_show_test_output_on_failure(self): def test_show_test_output_on_failure(self):
output = """ output = """
KTAP version 1 KTAP version 1
......
KTAP version 1
1..1
KTAP version 1
# Subtest: suite
# module: example
1..1
# test.speed: slow
ok 1 test
ok 1 suite
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment