Compare commits

...

22 Commits

Author SHA1 Message Date
Maschell 3c26ecd473 Fix DEBUG flag check in example plugin 2024-04-25 13:57:35 +02:00
Maschell 989b129834 Fix move operator for WUPSConfigItem 2024-04-25 13:57:35 +02:00
Maschell 5de93f178a Fix move operator for WUPSConfigCategory 2024-04-25 13:57:35 +02:00
Maschell 6b5c2ab95e StorageAPI: Make sure to call noexcept version of CreateSubItem in GetOrCreateSubItem 2024-04-25 13:57:35 +02:00
Maschell fe1c4aa671 Fix small typo in storage test 2024-04-25 13:57:35 +02:00
Maschell 284713c9f6 Fix WUPSConfigItemMultipleValues::CreateFromValue 2024-04-25 13:57:35 +02:00
Maschell c3f5bd8d7b StorageAPI: Add support for loading/storing empty strings/binaries 2024-04-25 13:57:35 +02:00
Maschell 83bb5e9d27 StorageAPI: Avoid -Wunused-parameter 2024-04-25 13:57:35 +02:00
Maschell 6f53d72e2d StorageAPI: Add different "modes" for reading binary and string data into an buffer (C++ API only) 2024-04-25 13:57:35 +02:00
Maschell f534559706 StorageAPI: Change WUPSStorageAPI_GetItemSize to add item type 2024-04-25 13:57:35 +02:00
Maschell 7ef3bfa9bc Add documentation of StorageAPI 2024-04-25 13:57:35 +02:00
Maschell 6879d15ce4 StorageAPI improvements 2024-04-25 13:57:35 +02:00
Maschell dba2b584fd Example: Update the existing example_plugin and add C++ example plugin 2024-04-25 13:57:35 +02:00
Maschell 332ec41488 Change version to 0.8.0 2024-04-25 13:57:35 +02:00
Maschell aed8b5af43 ConfigAPI: Some more changes, added C++ Wrapper 2024-04-25 13:57:35 +02:00
Maschell 4d9f28c1ae example_plugin: Update to use the new config api 2024-04-25 13:57:35 +02:00
Maschell ba3cd165dd New config API 2024-04-25 13:57:35 +02:00
Maschell d84fd56329 Free config items properly on early return 2024-04-25 13:57:35 +02:00
Maschell a56d1698b3 Implement __assert_func and __assert, improve abort() 2024-04-25 13:57:35 +02:00
Maschell 5fc4875b35 Publish dev docker images 2024-04-25 13:57:35 +02:00
Maschell c66d73efe4 Add storage_test_plugin 2024-04-25 13:57:35 +02:00
Maschell 58a2d3f734 Rewrite of the StorageAPI 2024-04-25 13:57:35 +02:00
329 changed files with 32349 additions and 1437 deletions

View File

@ -30,6 +30,8 @@ jobs:
- name: clang-format
run: |
docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./plugins/example_plugin/src
docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./plugins/example_plugin_cpp/src
docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./plugins/storage_test_plugin/src --exclude ./plugins/storage_test_plugin/src/catch2
build-examples:
runs-on: ubuntu-22.04
needs: clang-format-examples
@ -40,6 +42,10 @@ jobs:
docker build . -f Dockerfile.buildexamples -t builder
cd ./plugins/example_plugin
docker run --rm -v ${PWD}:/project builder make
cd ../example_plugin_cpp
docker run --rm -v ${PWD}:/project builder make
cd ../storage_test_plugin
docker run --rm -v ${PWD}:/project builder make
- uses: actions/upload-artifact@master
with:
name: binary

View File

@ -3,6 +3,7 @@ on:
push:
branches:
- main
- '*-dev'
env:
REGISTRY: ghcr.io
@ -31,7 +32,9 @@ jobs:
- uses: actions/checkout@v3
- name: clang-format
run: |
docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./plugins/storage_test_plugin/src --exclude ./plugins/storage_test_plugin/src/catch2
docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./plugins/example_plugin/src
docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./plugins/example_plugin_cpp/src
build-examples:
runs-on: ubuntu-22.04
needs: clang-format-examples
@ -42,6 +45,10 @@ jobs:
docker build . -f Dockerfile.buildexamples -t builder
cd ./plugins/example_plugin
docker run --rm -v ${PWD}:/project builder make
cd ../example_plugin_cpp
docker run --rm -v ${PWD}:/project builder make
cd ../storage_test_plugin
docker run --rm -v ${PWD}:/project builder make
build-and-push-image:
runs-on: ubuntu-latest
needs: [build-lib, build-examples]
@ -58,9 +65,10 @@ jobs:
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value={{date 'YYYYMMDD'}}-{{sha}}
type=raw,value={{date 'YYYYMMDD'}}
type=raw,value=latest
type=raw,value={{branch}}-{{date 'YYYYMMDD'}}-{{sha}},enable=${{ github.ref != format('refs/heads/{0}', 'main') }}
type=raw,value={{date 'YYYYMMDD'}}-{{sha}},enable={{is_default_branch}}
type=raw,value={{date 'YYYYMMDD'}},enable={{is_default_branch}}
type=raw,value=latest,enable={{is_default_branch}}
- name: Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@v2.1.0

View File

@ -2,8 +2,8 @@ TOPDIR ?= $(CURDIR)
include $(TOPDIR)/share/wups_rules
export WUPS_MAJOR := 0
export WUPS_MINOR := 7
export WUPS_PATCH := 1
export WUPS_MINOR := 8
export WUPS_PATCH := 0
VERSION := $(WUPS_MAJOR).$(WUPS_MINOR).$(WUPS_PATCH)
@ -16,23 +16,21 @@ VERSION := $(WUPS_MAJOR).$(WUPS_MINOR).$(WUPS_PATCH)
#---------------------------------------------------------------------------------
TARGET := wups
#BUILD := build
SOURCES := libraries/libwups/ \
libraries/libwups/utils
SOURCES := libraries/libwups/
DATA := data
INCLUDES := include
#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
CFLAGS := -g -Wall -Werror -save-temps \
CFLAGS := -g -O2 -Wall -Werror -save-temps \
-ffunction-sections -fdata-sections \
-fno-exceptions -fno-rtti \
$(MACHDEP) \
$(BUILD_CFLAGS)
CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__ -D__WUPS__
CXXFLAGS := $(CFLAGS) -std=gnu++20
CXXFLAGS := $(CFLAGS) -std=c++20
ASFLAGS := -g $(MACHDEP)
@ -40,7 +38,7 @@ ASFLAGS := -g $(MACHDEP)
# list of directories containing libraries, this must be the top level containing
# include and lib
#---------------------------------------------------------------------------------
LIBDIRS :=
LIBDIRS := $(WUT_ROOT)
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
@ -104,10 +102,10 @@ lib:
@[ -d $@ ] || mkdir -p $@
release:
@[ -d $@ ] || mkdir -p $@
@$(shell [ ! -d 'release' ] && mkdir -p 'release')
debug:
@[ -d $@ ] || mkdir -p $@
@$(shell [ ! -d 'debug' ] && mkdir -p 'debug')
lib/libwups.a : lib release $(SOURCES) $(INCLUDES)
@$(MAKE) BUILD=release OUTPUT=$(CURDIR)/$@ \

View File

@ -28,4 +28,4 @@ It's highly recommended to pin the version to the **latest date** instead of usi
## Format the code via docker
`docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./include ./libraries ./plugins/example_plugin/src -i`
`docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./include ./libraries ./plugins/example_plugin/src ./plugins/example_plugin_cpp/src ./plugins/storage_test_plugin/src --exclude ./plugins/storage_test_plugin/src/catch2 -i`

View File

@ -27,8 +27,9 @@
#include "wups/common.h"
#include "wups/config.h"
#include "wups/config_imports.h"
#include "wups/config_api.h"
#include "wups/function_patching.h"
#include "wups/hooks.h"
#include "wups/meta.h"
#include "wups/storage.h"
#include "wups/wups_debug.h"

View File

@ -17,26 +17,74 @@
#pragma once
#include <padscore/kpad.h>
#include <stdint.h>
#include <vpad/input.h>
#define WUPS_CONFIG_BUTTON_NONE 0
#define WUPS_CONFIG_BUTTON_LEFT (1 << 0)
#define WUPS_CONFIG_BUTTON_RIGHT (1 << 1)
#define WUPS_CONFIG_BUTTON_UP (1 << 2)
#define WUPS_CONFIG_BUTTON_DOWN (1 << 3)
#define WUPS_CONFIG_BUTTON_A (1 << 4)
#define WUPS_CONFIG_BUTTON_B (1 << 5)
#define WUPS_CONFIG_BUTTON_ZL (1 << 6)
#define WUPS_CONFIG_BUTTON_ZR (1 << 7)
#define WUPS_CONFIG_BUTTON_L (1 << 8)
#define WUPS_CONFIG_BUTTON_R (1 << 9)
#define WUPS_CONFIG_BUTTON_X (1 << 10)
#define WUPS_CONFIG_BUTTON_Y (1 << 11)
#define WUPS_CONFIG_BUTTON_STICK_L (1 << 12)
#define WUPS_CONFIG_BUTTON_STICK_R (1 << 13)
#define WUPS_CONFIG_BUTTON_PLUS (1 << 14)
#define WUPS_CONFIG_BUTTON_MINUS (1 << 15)
typedef int32_t WUPSConfigButtons;
typedef uint32_t WUPSConfigButtons;
typedef enum WUPS_CONFIG_SIMPLE_INPUT {
WUPS_CONFIG_BUTTON_NONE = 0,
WUPS_CONFIG_BUTTON_LEFT = (1 << 0),
WUPS_CONFIG_BUTTON_RIGHT = (1 << 1),
WUPS_CONFIG_BUTTON_UP = (1 << 2),
WUPS_CONFIG_BUTTON_DOWN = (1 << 3),
WUPS_CONFIG_BUTTON_A = (1 << 4),
WUPS_CONFIG_BUTTON_B = (1 << 5),
WUPS_CONFIG_BUTTON_ZL = (1 << 6),
WUPS_CONFIG_BUTTON_ZR = (1 << 7),
WUPS_CONFIG_BUTTON_L = (1 << 8),
WUPS_CONFIG_BUTTON_R = (1 << 9),
WUPS_CONFIG_BUTTON_X = (1 << 10),
WUPS_CONFIG_BUTTON_Y = (1 << 11),
WUPS_CONFIG_BUTTON_STICK_L = (1 << 12),
WUPS_CONFIG_BUTTON_STICK_R = (1 << 13),
WUPS_CONFIG_BUTTON_PLUS = (1 << 14),
WUPS_CONFIG_BUTTON_MINUS = (1 << 15),
} WUPS_CONFIG_SIMPLE_INPUT;
typedef struct {
WUPS_CONFIG_SIMPLE_INPUT buttons_h;
WUPS_CONFIG_SIMPLE_INPUT buttons_d;
WUPS_CONFIG_SIMPLE_INPUT buttons_r;
bool validPointer;
bool touched;
float pointerAngle;
int32_t x;
int32_t y;
} WUPSConfigSimplePadData;
typedef struct {
struct {
VPADReadError vpadError;
VPADTouchData tpCalib;
VPADStatus data;
} vpad;
struct {
KPADError kpadError[4];
KPADStatus data[4];
} kpad;
} WUPSConfigComplexPadData;
typedef enum WUPSConfigAPIStatus {
WUPSCONFIG_API_RESULT_SUCCESS = 0,
WUPSCONFIG_API_RESULT_INVALID_ARGUMENT = -0x01,
WUPSCONFIG_API_RESULT_OUT_OF_MEMORY = -0x03,
WUPSCONFIG_API_RESULT_NOT_FOUND = -0x06,
WUPSCONFIG_API_RESULT_INVALID_PLUGIN_IDENTIFIER = -0x70,
WUPSCONFIG_API_RESULT_MISSING_CALLBACK = -0x71,
WUPSCONFIG_API_RESULT_MODULE_NOT_FOUND = -0x80,
WUPSCONFIG_API_RESULT_MODULE_MISSING_EXPORT = -0x81,
WUPSCONFIG_API_RESULT_UNSUPPORTED_VERSION = -0x82,
WUPSCONFIG_API_RESULT_UNSUPPORTED_COMMAND = -0x83,
WUPSCONFIG_API_RESULT_LIB_UNINITIALIZED = -0x84,
WUPSCONFIG_API_RESULT_UNKNOWN_ERROR = -0x100,
} WUPSConfigAPIStatus;
typedef enum WUPSConfigAPICallbackStatus {
WUPSCONFIG_API_CALLBACK_RESULT_SUCCESS = 0,
WUPSCONFIG_API_CALLBACK_RESULT_ERROR = -1,
} WUPSConfigAPICallbackStatus;
typedef struct {
int32_t (*getCurrentValueDisplay)(void *context, char *out_buf, int32_t out_size);
@ -54,8 +102,195 @@ typedef struct {
void (*onButtonPressed)(void *context, WUPSConfigButtons button);
void (*onDelete)(void *context);
} WUPSConfigCallbacks_t;
} WUPSConfigAPIItemCallbacksV1;
typedef uint32_t WUPSConfigItemHandle;
typedef uint32_t WUPSConfigHandle;
typedef uint32_t WUPSConfigCategoryHandle;
typedef struct {
/**
* Set the string which is displayed for an item
* @param context The context which has been passed to the item during creating
* @param out_buf Buffer where the string should be written to
* @param out_size Size of out_buf
*
* \result non-zero result indicates an error.
*/
int32_t (*getCurrentValueDisplay)(void *context, char *out_buf, int32_t out_size);
/**
* Set the string which is displayed for an item when the cursor is on is item
* @param context The context which has been passed to the item during creating
* @param out_buf Buffer where the string should be written to
* @param out_size Size of out_buf
*
* \result non-zero result indicates an error.
*/
int32_t (*getCurrentValueSelectedDisplay)(void *context, char *out_buf, int32_t out_size);
/**
* Called when the cursor enters or leaves this item
* @param context The context which has been passed to the item during creating
* @param isSelected True if the item cursor is now pointing to this item, \n
* False if it's not pointing to this item anymore
*/
void (*onSelected)(void *context, bool isSelected);
/**
* Called when the current value of this item should be set to the default value
* @param context The context which has been passed to the item during creating
*/
void (*restoreDefault)(void *context);
/**
* Determines if movement to different item is allowed.
* @param context The context which has been passed to the item during creating
* \return True if it should be not possible to select a different item or exit the current category \n
* False if it should be possible to select a different item or exit the current category
*/
bool (*isMovementAllowed)(void *context);
/**
* Called when the config menu has been closed
* @param context The context which has been passed to the item during creating
*/
void (*onCloseCallback)(void *context);
/**
* This function is called on each frame and provides information about the current inputs.
* The inputs are simplified and all 5 possible controller inputs (from Gamepad and up to 4 Wiimotes/Pro Controller)
* are unified in this single unified struct.
*
* @param context The context which has been passed to the item during creating
* @param input Simplified version of the current inputs
*
* \note To get the full input for all possible controllers see "onInputEx"
*
* @see onInputEx
*/
void (*onInput)(void *context, WUPSConfigSimplePadData input);
/**
* This function is called on each frame and provides information about the current inputs.
* The structs contains information for current individual Gampepad and Wiimote/Pro Contoller inputs.
*
* @param context The context which has been passed to the item during creating
* @param input current input for all possibles controller
*
* \note To get a simplified input callback that combines all controller into a single struct see "onInput"
*
* @see onInput
*/
void (*onInputEx)(void *context, WUPSConfigComplexPadData input);
/**
* This function is called when the item is about to be deleted. It can be used to free any alloated memory.
*
* @param context The context which has been passed to the item during creating
*/
void (*onDelete)(void *context);
} WUPSConfigAPIItemCallbacksV2;
#define WUPS_API_ITEM_OPTION_VERSION_V1 1
#define WUPS_API_ITEM_OPTION_VERSION_V2 2
typedef struct WUPSConfigAPIItemOptionsV1 {
const char *configId;
const char *displayName;
void *context;
WUPSConfigAPIItemCallbacksV1 callbacks;
} WUPSConfigAPIItemOptionsV1;
typedef struct WUPSConfigAPIItemOptionsV2 {
const char *displayName;
void *context;
WUPSConfigAPIItemCallbacksV2 callbacks;
} WUPSConfigAPIItemOptionsV2;
typedef struct WUPSConfigAPICreateItemOptions {
uint32_t version;
union {
WUPSConfigAPIItemOptionsV1 v1;
WUPSConfigAPIItemOptionsV2 v2;
} data;
} WUPSConfigAPICreateItemOptions;
typedef uint32_t WUPSConfigAPIVersion;
typedef struct WUPSConfigItemHandle {
void *handle;
#ifdef __cplusplus
WUPSConfigItemHandle() {
handle = nullptr;
}
explicit WUPSConfigItemHandle(void *handle) : handle(handle) {}
bool operator==(const WUPSConfigItemHandle other) const {
return handle == other.handle;
}
bool operator==(const void *other) const {
return handle == other;
}
#endif
} WUPSConfigItemHandle;
typedef struct WUPSConfigHandle {
void *handle;
#ifdef __cplusplus
WUPSConfigHandle() {
handle = nullptr;
}
explicit WUPSConfigHandle(void *handle) : handle(handle) {}
bool operator==(const WUPSConfigHandle other) const {
return handle == other.handle;
}
bool operator==(const void *other) const {
return handle == other;
}
#endif
} WUPSConfigHandle;
typedef struct WUPSConfigCategoryHandle {
void *handle;
#ifdef __cplusplus
WUPSConfigCategoryHandle() {
handle = nullptr;
}
explicit WUPSConfigCategoryHandle(void *handle) : handle(handle) {}
bool operator==(const WUPSConfigCategoryHandle other) const {
return handle == other.handle;
}
bool operator==(const void *other) const {
return handle == other;
}
#endif
} WUPSConfigCategoryHandle;
#define WUPS_API_CATEGORY_OPTION_VERSION_V1 1
typedef struct WUPSConfigAPICreateCategoryOptionsV1 {
const char *name;
} WUPSConfigAPICreateCategoryOptionsV1;
typedef struct WUPSConfigAPICreateCategoryOptions {
uint32_t version;
union {
WUPSConfigAPICreateCategoryOptionsV1 v1;
} data;
} WUPSConfigAPICreateCategoryOptions;
#define WUPS_API_CONFIG_API_OPTION_VERSION_V1 1
typedef struct WUPSConfigAPIOptionsV1 {
const char *name;
} WUPSConfigAPIOptionsV1;
typedef struct WUPSConfigAPIOptions {
uint32_t version;
union {
WUPSConfigAPIOptionsV1 v1;
} data;
} WUPSConfigAPIOptions;
#define WUPS_CONFIG_API_VERSION_ERROR 0xFFFFFFFF
typedef struct wups_loader_init_config_args_t {
uint32_t arg_version;
uint32_t plugin_identifier;
} wups_loader_init_config_args_t;

View File

@ -0,0 +1,52 @@
#pragma once
#ifdef __cplusplus
#include "WUPSConfigItem.h"
#include <optional>
#include <stdexcept>
#include <string>
class WUPSConfigCategory {
public:
explicit WUPSConfigCategory(WUPSConfigCategoryHandle handle) noexcept;
virtual ~WUPSConfigCategory();
WUPSConfigCategory(const WUPSConfigCategory &) = delete;
WUPSConfigCategory(WUPSConfigCategory &&src) noexcept : mHandle(src.mHandle) {
src.mHandle = {};
}
WUPSConfigCategory &operator=(WUPSConfigCategory &&src) noexcept {
if (this != &src) {
this->mHandle = src.mHandle;
src.mHandle = {};
}
return *this;
}
static std::optional<WUPSConfigCategory> Create(std::string_view name, WUPSConfigAPIStatus &error) noexcept;
static WUPSConfigCategory Create(std::string_view name);
bool add(WUPSConfigCategory &&cat, WUPSConfigAPIStatus &error) noexcept;
void add(WUPSConfigCategory &&cat);
bool add(WUPSConfigItem &&item, WUPSConfigAPIStatus &error) noexcept;
void add(WUPSConfigItem &&item);
[[nodiscard]] const WUPSConfigCategoryHandle &getHandle() const {
return mHandle;
}
void release() {
mHandle = {};
}
private:
WUPSConfigCategoryHandle mHandle = {};
};
#endif

View File

@ -0,0 +1,42 @@
#pragma once
#ifdef __cplusplus
#include "wups/config.h"
#include <coreinit/debug.h>
class WUPSConfigItem {
protected:
explicit WUPSConfigItem(WUPSConfigItemHandle itemHandle) : mHandle(itemHandle) {
}
public:
virtual ~WUPSConfigItem();
WUPSConfigItem(const WUPSConfigItem &) = delete;
WUPSConfigItem(WUPSConfigItem &&src) noexcept : mHandle(src.mHandle) {
src.mHandle = {};
}
WUPSConfigItem &operator=(WUPSConfigItem &&src) noexcept {
if (this != &src) {
this->mHandle = src.mHandle;
src.mHandle = {};
}
return *this;
}
[[nodiscard]] const WUPSConfigItemHandle &getHandle() const {
return mHandle;
}
void release() {
mHandle = {};
}
private:
WUPSConfigItemHandle mHandle = {};
};
#endif

View File

@ -1,41 +1,131 @@
#include <wups.h>
#pragma once
#include <wups/config.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct ConfigItemBoolean {
char *configId;
WUPSConfigItemHandle handle;
const char *identifier;
bool defaultValue;
bool valueAtCreation;
bool value;
char trueValue[32];
char falseValue[32];
void *callback;
void *valueChangedCallback;
} ConfigItemBoolean;
typedef void (*BooleanValueChangedCallback)(ConfigItemBoolean *, bool);
bool WUPSConfigItemBoolean_AddToCategory(WUPSConfigCategoryHandle cat, const char *configId, const char *displayName, bool defaultValue, BooleanValueChangedCallback callback);
bool WUPSConfigItemBoolean_AddToCategoryEx(WUPSConfigCategoryHandle cat, const char *configId, const char *displayName, bool defaultValue, BooleanValueChangedCallback callback, const char *trueValue,
const char *falseValue);
WUPSConfigAPIStatus
WUPSConfigItemBoolean_CreateEx(const char *identifier,
const char *displayName,
bool defaultValue,
bool currentValue,
BooleanValueChangedCallback callback,
const char *trueValue,
const char *falseValue,
WUPSConfigItemHandle *outHandle);
#define WUPSConfigItemBoolean_AddToCategoryHandled(__config__, __cat__, __configIs__, __displayName__, __defaultValue__, __callback__) \
do { \
if (!WUPSConfigItemBoolean_AddToCategory(__cat__, __configIs__, __displayName__, __defaultValue__, __callback__)) { \
WUPSConfig_Destroy(__config__); \
return 0; \
} \
} while (0)
#define WUPSConfigItemBoolean_AddToCategoryHandledEx(__config__, __cat__, __configID__, __displayName__, __defaultValue__, __callback__, __trueValue__, __falseValue__) \
do { \
if (!WUPSConfigItemBoolean_AddToCategoryEx(__cat__, __configID__, __displayName__, __defaultValue__, __callback__, __trueValue__, __falseValue__)) { \
WUPSConfig_Destroy(__config__); \
return 0; \
} \
} while (0)
/**
* @brief Adds a boolean configuration item to the specified category.
*
* This function adds a boolean configuration item to the given category. The item is displayed with a specified display name.
* The default value and current value of the item are set to the provided values. A callback function is called whenever
* the value of the item changes.
*
* @param cat The handle of the category to add the item to.
* @param identifier Optional identifier for the item. Can be NULL.
* @param displayName The display name of the item.
* @param defaultValue The default value of the item.
* @param currentValue The current value of the item.
* @param callback A callback function that will be called when the config menu closes and the value of the item has been changed.
* @return True if the item was added successfully, false otherwise.
*/
WUPSConfigAPIStatus
WUPSConfigItemBoolean_AddToCategory(WUPSConfigCategoryHandle cat,
const char *identifier,
const char *displayName,
bool defaultValue,
bool currentValue,
BooleanValueChangedCallback callback);
/**
* @brief Adds a boolean configuration item to the specified category.
*
* This function adds a boolean configuration item to the given category. The item is displayed with a specified display name.
* The default value and current value of the item are set to the provided values. A callback function is called whenever
* the value of the item changes.
*
* @param cat The handle of the category to add the item to.
* @param identifier Optional identifier for the item. Can be NULL.
* @param displayName The display name of the item.
* @param defaultValue The default value of the item.
* @param currentValue The current value of the item.
* @param callback A callback function that will be called when the config menu closes and the value of the item has been changed.
* @param trueValue The string representation of the true value.
* @param falseValue The string representation of the false value.
* @return True if the item was successfully added to the category, false otherwise.
*/
WUPSConfigAPIStatus
WUPSConfigItemBoolean_AddToCategoryEx(WUPSConfigCategoryHandle cat,
const char *identifier,
const char *displayName,
bool defaultValue,
bool currentValue,
BooleanValueChangedCallback callback,
const char *trueValue,
const char *falseValue);
#ifdef __cplusplus
}
#endif
#ifdef __cplusplus
#include "WUPSConfigItem.h"
#include <optional>
#include <stdexcept>
#include <string>
#include <wups/config_api.h>
class WUPSConfigItemBoolean : public WUPSConfigItem {
public:
static std::optional<WUPSConfigItemBoolean> CreateEx(std::optional<std::string> identifier,
std::string_view displayName,
bool defaultValue,
bool currentValue,
BooleanValueChangedCallback callback,
std::string_view trueValue,
std::string_view falseValue,
WUPSConfigAPIStatus &err) noexcept;
static WUPSConfigItemBoolean CreateEx(std::optional<std::string> identifier,
std::string_view displayName,
bool defaultValue,
bool currentValue,
BooleanValueChangedCallback callback,
std::string_view trueValue,
std::string_view falseValue);
static std::optional<WUPSConfigItemBoolean> Create(std::optional<std::string> identifier,
std::string_view displayName,
bool defaultValue,
bool currentValue,
BooleanValueChangedCallback callback,
WUPSConfigAPIStatus &err) noexcept;
static WUPSConfigItemBoolean Create(std::optional<std::string> identifier,
std::string_view displayName,
bool defaultValue,
bool currentValue,
BooleanValueChangedCallback callback);
private:
explicit WUPSConfigItemBoolean(WUPSConfigItemHandle itemHandle) : WUPSConfigItem(itemHandle) {
}
};
#endif

View File

@ -1,33 +1,92 @@
#include <wups.h>
#pragma once
#include <wups/config.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct ConfigItemIntegerRange {
char *configId;
WUPSConfigItemHandle handle;
const char *identifier;
int defaultValue;
int value;
int valueAtCreation;
int minValue;
int maxValue;
void *callback;
void *valueChangedCallback;
} ConfigItemIntegerRange;
typedef void (*IntegerRangeValueChangedCallback)(ConfigItemIntegerRange *, int32_t);
bool WUPSConfigItemIntegerRange_AddToCategory(WUPSConfigCategoryHandle cat, const char *configId, const char *displayName,
int32_t defaultValue, int32_t minValue, int32_t maxValue,
IntegerRangeValueChangedCallback callback);
WUPSConfigAPIStatus
WUPSConfigItemIntegerRange_Create(const char *identifier,
const char *displayName,
int32_t defaultValue, int32_t currentValue,
int32_t minValue, int32_t maxValue,
IntegerRangeValueChangedCallback callback,
WUPSConfigItemHandle *outHandle);
#define WUPSConfigItemIntegerRange_AddToCategoryHandled(__config__, __cat__, __configId__, __displayName__, __defaultValue__, __minValue__, __maxValue__, __callback__) \
do { \
if (!WUPSConfigItemIntegerRange_AddToCategory(__cat__, __configId__, __displayName__, __defaultValue__, __minValue__, __maxValue__, __callback__)) { \
WUPSConfig_Destroy(__config__); \
return 0; \
} \
} while (0)
/**
* \brief Adds an integer range configuration item to a category.
*
* This function creates a new ConfigItemIntegerRange item and adds it to the specified category.
* The item represents an integer value within a specified range, and allows the user to modify the value.
*
* \param cat The category handle to which the item should be added.
* \param identifier Optional identifier for the item. Can be NULL.
* \param displayName The display name for the item.
* \param defaultValue The default value for the item.
* \param currentValue The current value for the item.
* \param minValue The minimum value allowed for the item.
* \param maxValue The maximum value allowed for the item.
* \param callback A callback function that will be called when the config menu closes and the value of the item has been changed.
*
* \return Returns true if the item was successfully added to the category, false otherwise.
*
* @note The defaultValue and currentValue must in the specified range.
*/
WUPSConfigAPIStatus
WUPSConfigItemIntegerRange_AddToCategory(WUPSConfigCategoryHandle cat,
const char *identifier,
const char *displayName,
int32_t defaultValue, int32_t currentValue,
int32_t minValue, int32_t maxValue,
IntegerRangeValueChangedCallback callback);
#ifdef __cplusplus
}
#endif
#endif
#ifdef __cplusplus
#include "WUPSConfigItem.h"
#include <optional>
#include <span>
#include <stdexcept>
#include <string>
#include <wups/config_api.h>
class WUPSConfigItemIntegerRange : public WUPSConfigItem {
public:
static std::optional<WUPSConfigItemIntegerRange> Create(
std::optional<std::string> identifier,
std::string_view displayName,
int32_t defaultValue, int32_t currentValue,
int32_t minValue, int32_t maxValue,
IntegerRangeValueChangedCallback valuesChangedCallback,
WUPSConfigAPIStatus &err) noexcept;
static WUPSConfigItemIntegerRange Create(
std::optional<std::string> identifier,
std::string_view displayName,
int32_t defaultValue, int32_t currentValue,
int32_t minValue, int32_t maxValue,
IntegerRangeValueChangedCallback valuesChangedCallback);
private:
explicit WUPSConfigItemIntegerRange(WUPSConfigItemHandle itemHandle) : WUPSConfigItem(itemHandle) {
}
};
#endif

View File

@ -1,4 +1,8 @@
#include <wups.h>
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <wups/config.h>
#ifdef __cplusplus
extern "C" {
@ -6,32 +10,107 @@ extern "C" {
typedef struct ConfigItemMultipleValuesPair {
uint32_t value;
char *valueName;
const char *valueName;
} ConfigItemMultipleValuesPair;
typedef struct ConfigItemMultipleValues {
char *configId;
WUPSConfigItemHandle handle;
const char *identifier;
int32_t defaultValueIndex;
int32_t valueIndex;
void *callback;
int32_t valueIndexAtCreation;
ConfigItemMultipleValuesPair *values;
int valueCount;
void *valueChangedCallback;
} ConfigItemMultipleValues;
typedef void (*MultipleValuesChangedCallback)(ConfigItemMultipleValues *, uint32_t);
bool WUPSConfigItemMultipleValues_AddToCategory(WUPSConfigCategoryHandle cat, const char *configId, const char *displayName, int defaultValueIndex, ConfigItemMultipleValuesPair *possibleValues,
int pairCount, MultipleValuesChangedCallback callback);
#define WUPSConfigItemMultipleValues_AddToCategoryHandled(__config__, __cat__, __configID__, __displayName__, __defaultValueIndex__, __possibleValues__, __pairCount__, __callback__) \
do { \
if (!WUPSConfigItemMultipleValues_AddToCategory(__cat__, __configID__, __displayName__, __defaultValueIndex__, __possibleValues__, __pairCount__, __callback__)) { \
WUPSConfig_Destroy(__config__); \
return 0; \
} \
} while (0)
WUPSConfigAPIStatus
WUPSConfigItemMultipleValues_Create(const char *identifier, const char *displayName,
int defaultValueIndex, int currentValueIndex,
ConfigItemMultipleValuesPair *possibleValues, int pairCount,
MultipleValuesChangedCallback callback,
WUPSConfigItemHandle *outHandle);
/**
* @brief Add a multiple values configuration item to a category.
*
* This function adds a multiple values configuration item to a specified category.
* The item will be displayed in the configuration menu with the provided display name.
*
* @param cat The handle of the category where the item should be added.
* @param identifier The identifier of the item. It is used to uniquely identify the item.
* @param displayName The display name of the item. It will be shown in the configuration menu.
* @param defaultValueIndex The index of the default value in the array of possible values.
* @param currentValueIndex The index of the current value in the array of possible values.
* @param possibleValues An array of possible values for the item.
* @param pairCount The number of pairs (value and value name) in the possibleValues array.
* @param callback A callback function that will be called when the config menu closes and the value of the item has been changed.
*
* @return true if the item was successfully added to the category, false otherwise.
*
* @note The defaultValueIndex and currentValueIndex must be valid for the given pairCount.
*/
WUPSConfigAPIStatus
WUPSConfigItemMultipleValues_AddToCategory(WUPSConfigCategoryHandle cat, const char *identifier, const char *displayName,
int defaultValueIndex, int currentValueIndex,
ConfigItemMultipleValuesPair *possibleValues, int pairCount,
MultipleValuesChangedCallback callback);
#ifdef __cplusplus
}
#endif
#ifdef __cplusplus
#include "WUPSConfigItem.h"
#include <optional>
#include <span>
#include <stdexcept>
#include <string>
#include <wups/config_api.h>
class WUPSConfigItemMultipleValues : public WUPSConfigItem {
public:
struct ValuePair {
uint32_t value;
std::string_view name;
};
static std::optional<WUPSConfigItemMultipleValues> CreateFromIndex(std::optional<const std::string> identifier,
std::string_view displayName,
int defaultValueIndex, int currentValueIndex,
const std::span<const ValuePair> &possibleValues,
MultipleValuesChangedCallback valuesChangedCallback,
WUPSConfigAPIStatus &err) noexcept;
static WUPSConfigItemMultipleValues CreateFromIndex(std::optional<const std::string> identifier,
std::string_view displayName,
int defaultValueIndex, int currentValueIndex,
const std::span<const ValuePair> &possibleValues,
MultipleValuesChangedCallback valuesChangedCallback);
static std::optional<WUPSConfigItemMultipleValues> CreateFromValue(
std::optional<const std::string> identifier,
std::string_view displayName,
uint32_t defaultValue, uint32_t currentValue,
const std::span<const ValuePair> &possibleValues,
MultipleValuesChangedCallback valuesChangedCallback,
WUPSConfigAPIStatus &err) noexcept;
static WUPSConfigItemMultipleValues CreateFromValue(
std::optional<const std::string> identifier,
std::string_view displayName,
int32_t defaultValue, int32_t currentValue,
const std::span<const ValuePair> &possibleValues,
MultipleValuesChangedCallback valuesChangedCallback);
private:
explicit WUPSConfigItemMultipleValues(WUPSConfigItemHandle itemHandle) : WUPSConfigItem(itemHandle) {
}
};
#endif

View File

@ -1,4 +1,6 @@
#include <wups.h>
#pragma once
#include <wups/config.h>
#ifdef __cplusplus
extern "C" {
@ -8,16 +10,39 @@ typedef struct ConfigItemStub {
WUPSConfigItemHandle handle;
} ConfigItemStub;
bool WUPSConfigItemStub_AddToCategory(WUPSConfigCategoryHandle cat, const char *configID, const char *displayName);
WUPSConfigAPIStatus WUPSConfigItemStub_Create(const char *displayName,
WUPSConfigItemHandle *outHandle);
#define WUPSConfigItemStub_AddToCategoryHandled(__config__, __cat__, __configID__, __displayName__) \
do { \
if (!WUPSConfigItemStub_AddToCategory(__cat__, __configID__, __displayName__)) { \
WUPSConfig_Destroy(__config__); \
return 0; \
} \
} while (0)
/**
* @brief Adds a stub item to a category to display information
*
* @param cat The handle of the category to which the item should be added.
* @param displayName The display name of the item.
* @return true if the item was added successfully, false otherwise.
*/
WUPSConfigAPIStatus
WUPSConfigItemStub_AddToCategory(WUPSConfigCategoryHandle cat, const char *displayName);
#ifdef __cplusplus
}
#endif
#endif
#ifdef __cplusplus
#include "WUPSConfigItem.h"
#include <optional>
#include <stdexcept>
#include <string>
#include <wups/config_api.h>
class WUPSConfigItemStub : public WUPSConfigItem {
public:
static std::optional<WUPSConfigItemStub> Create(std::string_view displayName,
WUPSConfigAPIStatus &err) noexcept;
static WUPSConfigItemStub Create(std::string_view displayName);
private:
explicit WUPSConfigItemStub(WUPSConfigItemHandle itemHandle) : WUPSConfigItem(itemHandle) {
}
};
#endif

213
include/wups/config_api.h Normal file
View File

@ -0,0 +1,213 @@
#pragma once
#include "config.h"
#include "config/WUPSConfigCategory.h"
#include "config/WUPSConfigItem.h"
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef WUPSConfigAPICallbackStatus (*WUPSConfigAPI_MenuOpenedCallback)(WUPSConfigCategoryHandle root);
typedef void (*WUPSConfigAPI_MenuClosedCallback)();
/**
* @brief Initialize the WUPSConfigAPI with extended options. For internal use.
* @see WUPSConfigAPI_Init instead
*/
WUPSConfigAPIStatus WUPSConfigAPI_InitEx(uint32_t pluginIdentifier, WUPSConfigAPIOptions, WUPSConfigAPI_MenuOpenedCallback, WUPSConfigAPI_MenuClosedCallback);
/**
* @brief Initializes the WUPSConfigAPI with the given options, opened callback, and closed callback.
*
* This function initializes the WUPSConfigAPI with the provided options, opened callback, and closed callback.
* If the initialization is successful, it returns WUPSCONFIG_API_RESULT_SUCCESS. Otherwise, it returns
* an appropriate error code indicating the reason for failure.
*
* @param optionsV1 The options for the WUPSConfigAPI. It contains the name of the plugin.
* @param openedCallback The callback function to be called when the menu is opened.
* @param closedCallback The callback function to be called when the menu is closed.
*
* @return WUPSConfigAPIStatus WUPSCONFIG_API_RESULT_SUCCESS if initialization is successful,
* otherwise an appropriate error code.
* - WUPSCONFIG_API_RESULT_SUCCESS: The initialization was successful.
* - WUPSCONFIG_API_RESULT_INVALID_ARGUMENT: The `openedCallback` or `closedCallback` parameter is nullptr.
* - WUPSCONFIG_API_RESULT_UNSUPPORTED_VERSION: The specified `options.version` is not supported.
* - WUPSCONFIG_API_RESULT_NOT_FOUND: The plugin with the given identifier was not found
* - WUPSCONFIG_API_RESULT_LIB_UNINITIALIZED: The API has not been initialized.
* - WUPSCONFIG_API_RESULT_UNSUPPORTED_COMMAND: The command is not supported.
*/
WUPSConfigAPIStatus WUPSConfigAPI_Init(WUPSConfigAPIOptionsV1 optionsV1, WUPSConfigAPI_MenuOpenedCallback openedCallback, WUPSConfigAPI_MenuClosedCallback closedCallback);
/**
* @brief Retrieves the version of the WUPSConfigAPI library.
*
* This function retrieves the version of the WUPSConfigAPI library and stores it in the provided output variable.
*
* @param[out] outVariable Pointer to a WUPSConfigAPIVersion variable to store the library version.
* @return Returns the status of the API call.
* - WUPSCONFIG_API_RESULT_SUCCESS: The version was retrieved successfully.
* - WUPSCONFIG_API_RESULT_MODULE_NOT_FOUND: The module containing the WUPSConfigAPI_GetVersion function was not found.
* - WUPSCONFIG_API_RESULT_MODULE_MISSING_EXPORT: The WUPSConfigAPI_GetVersion function was not found in the module.
*/
WUPSConfigAPIStatus WUPSConfigAPI_GetVersion(WUPSConfigAPIVersion *outVariable);
/**
* @brief Create a new category with extended options. Internal use.
* @see WUPSConfigAPI_Category_Create
**/
WUPSConfigAPIStatus WUPSConfigAPI_Category_CreateEx(WUPSConfigAPICreateCategoryOptions options, WUPSConfigCategoryHandle *out);
/**
* @brief Create a new category.
*
* This function creates a new category using the given options. The options struct contains the category name.
* If the category creation is successful, the category handle is returned through the *out parameter.
*
* @param options The options for creating the category.
* @param[out] out A pointer to a WUPSConfigCategoryHandle variable that will receive the category handle.
* @return WUPSConfigAPIStatus The status of the category creation operation.
* - WUPSCONFIG_API_RESULT_SUCCESS: Success.
* - WUPSCONFIG_API_RESULT_INVALID_ARGUMENT: Invalid parameter, `out` or `name` is NULL.
* - WUPSCONFIG_API_RESULT_LIB_UNINITIALIZED: The API has not been initialized.
* - WUPSCONFIG_API_RESULT_UNSUPPORTED_COMMAND: The command is not supported.
*
* @note The function internally calls WUPSConfigAPI_Category_CreateEx().
* @note The caller is responsible for deleting the WUPSConfigCategoryHandle instance unless it has been transferred to
* a different category
*/
static inline WUPSConfigAPIStatus WUPSConfigAPI_Category_Create(WUPSConfigAPICreateCategoryOptionsV1 options, WUPSConfigCategoryHandle *out) {
WUPSConfigAPICreateCategoryOptions optionsWrapper = {
.version = WUPS_API_CATEGORY_OPTION_VERSION_V1,
.data = {.v1 = options},
};
return WUPSConfigAPI_Category_CreateEx(optionsWrapper, out);
}
/**
* @brief Destroys a WUPSConfigCategoryHandle.
*
* This function is used to destroy a WUPSConfigCategoryHandle object.
*
* @param handle The WUPSConfigCategoryHandle to be destroyed.
* @return WUPSConfigAPIStatus Returns the status of the API call:
* - WUPSCONFIG_API_RESULT_SUCCESS: The WUPSConfigCategoryHandle was successfully destroyed.
* - WUPSCONFIG_API_RESULT_INVALID_ARGUMENT: The handle is nullptr.
* - WUPSCONFIG_API_RESULT_LIB_UNINITIALIZED: The API has not been initialized.
* - WUPSCONFIG_API_RESULT_UNSUPPORTED_COMMAND: The command is not supported.
*
* @note The caller is responsible for deleting the WUPSConfigCategoryHandle instance unless it has been transferred to
* a different category
*/
WUPSConfigAPIStatus WUPSConfigAPI_Category_Destroy(WUPSConfigCategoryHandle handle);
/**
* @brief Adds a category to the parent category.
*
* This function is used to add a category to an existing parent category.
*
* @param parentHandle The handle to the parent category.
* @param categoryHandle The handle to the category to be added.
*
* @return WUPSConfigAPIStatus The status code indicating the result of the operation.
* Possible values are:
* - WUPSCONFIG_API_RESULT_SUCCESS: The category was added successfully.
* - WUPSCONFIG_API_RESULT_INVALID_ARGUMENT: The parentHandle or categoryHandle is null.
* - WUPSCONFIG_API_RESULT_LIB_UNINITIALIZED: The library is not initialized.
* - WUPSCONFIG_API_RESULT_UNSUPPORTED_COMMAND: The command is not supported.
* - WUPSCONFIG_API_RESULT_UNKNOWN_ERROR: An unknown error occurred.
*
* @note On success the ownership of the category will be transferred to the Category and the categoryHandle WUPSConfigCategoryHandle
* will be invalid.
*/
WUPSConfigAPIStatus WUPSConfigAPI_Category_AddCategory(WUPSConfigCategoryHandle parentHandle, WUPSConfigCategoryHandle categoryHandle);
/**
* @brief Adds an item to the given category.
*
* This function adds the specified item to the parent category. The parent
* category and item handles must be valid and non-zero.
*
* @param parentHandle The handle of the parent category.
* @param[out] itemHandle The handle of the item to be added.
* @return WUPSConfigAPIStatus The status code indicating the result of the operation.
* - WUPSCONFIG_API_RESULT_SUCCESS: If the item was added successfully.
* - WUPSCONFIG_API_RESULT_INVALID_ARGUMENT: If either the parentHandle or itemHandle is invalid.
* - WUPSCONFIG_API_RESULT_NOT_FOUND: If the parent category was not found.
* - WUPSCONFIG_API_RESULT_UNKNOWN_ERROR: If an unknown error occurred while adding the item.
* - WUPSCONFIG_API_RESULT_UNSUPPORTED_COMMAND: The provided command is not supported.
* - WUPSCONFIG_API_RESULT_LIB_UNINITIALIZED: The library is not initialized properly.
* @see WUPSConfigCategoryHandle
* @see WUPSConfigItemHandle
* @see WUPSConfigAPIStatus
*
* @note On success the ownership of the item will be transferred to the Category and the itemHandle WUPSConfigItemHandle
* will be invalid.
*/
WUPSConfigAPIStatus WUPSConfigAPI_Category_AddItem(WUPSConfigCategoryHandle parentHandle, WUPSConfigItemHandle itemHandle);
/**
* @brief Create a WUPSConfigAPI item with extended options. Internal use.
* @see WUPSConfigAPI_Item_Create
**/
WUPSConfigAPIStatus WUPSConfigAPI_Item_CreateEx(WUPSConfigAPICreateItemOptions options, WUPSConfigItemHandle *out);
/**
* @brief Creates a new configuration item using the provided options.
*
* This function creates a new configuration item using the provided options.
*
* @param options The options for creating the configuration item.
* @param[out] out The handle to the created configuration item.
* @return The status of the API call. Possible values are:
* - WUPSCONFIG_API_RESULT_SUCCESS if the item was created and added successfully.
* - WUPSCONFIG_API_RESULT_INVALID_ARGUMENT if the 'out' parameter is nullptr.
* - WUPSCONFIG_API_RESULT_UNSUPPORTED_VERSION if the options version is invalid.
* - WUPSCONFIG_API_RESULT_OUT_OF_MEMORY if memory allocation failed.
* - WUPSCONFIG_API_RESULT_UNSUPPORTED_COMMAND: The provided command is not supported.
* - WUPSCONFIG_API_RESULT_LIB_UNINITIALIZED: The library is not initialized properly.
*
* @note The caller is responsible for deleting the WUPSConfigItem instance unless it has been transferred to
* a category
*/
static inline WUPSConfigAPIStatus WUPSConfigAPI_Item_Create(WUPSConfigAPIItemOptionsV2 options, WUPSConfigItemHandle *out) {
WUPSConfigAPICreateItemOptions itemOptions = {
.version = WUPS_API_ITEM_OPTION_VERSION_V2,
.data = {.v2 = options},
};
return WUPSConfigAPI_Item_CreateEx(itemOptions, out);
}
/**
* @brief Destroy a WUPSConfigItemHandle.
*
* This function destroys a WUPSConfigItemHandle. It can only be called if the WUPSConfig API is initialized and the handle is valid.
* A item must not be destroyed if it's added to a WUPSConfigCategory
*
* @param handle The handle to be destroyed.
* @return WUPSConfigAPIStatus The status code indicating the result of the operation:
* - WUPSCONFIG_API_RESULT_SUCCESS: If the handle was successfully destroyed.
* - WUPSCONFIG_API_RESULT_NOT_FOUND: The WUPSConfigItem with the given handle was not found.
* - WUPSCONFIG_API_RESULT_INVALID_ARGUMENT: If the handle is invalid.
* - WUPSCONFIG_API_RESULT_LIB_UNINITIALIZED: If the WUPSConfig API is not initialized.
* - WUPSCONFIG_API_RESULT_UNSUPPORTED_COMMAND: If the destroy command is not supported by the API or the API version is too low.
*/
WUPSConfigAPIStatus WUPSConfigAPI_Item_Destroy(WUPSConfigItemHandle handle);
/**
* @brief Converts a WUPSConfigAPIStatus value to a corresponding string representation.
*
* This function takes a WUPSConfigAPIStatus value and returns a string representation of the value.
* The string representation is determined based on the given status value using a switch statement.
* If the status value is not recognized, a default string "WUPSCONFIG_API_RESULT_UNKNOWN_ERROR" is returned.
*
* @param status The WUPSConfigAPIStatus value to convert to string.
* @return The string representation of the given WUPSConfigAPIStatus value.
*/
const char *WUPSConfigAPI_GetStatusStr(WUPSConfigAPIStatus status);
#ifdef __cplusplus
}
#endif

View File

@ -1,63 +0,0 @@
#pragma once
#include "config.h"
#include "stdint.h"
#ifdef __cplusplus
extern "C" {
#endif
int32_t WUPSConfigItem_Create(WUPSConfigItemHandle *out, const char *configID, const char *displayName, WUPSConfigCallbacks_t callbacks, void *context);
int32_t WUPSConfigItem_Destroy(WUPSConfigItemHandle handle);
int32_t WUPSConfig_Destroy(WUPSConfigHandle handle);
int32_t WUPSConfigItem_SetDisplayName(WUPSConfigItemHandle handle, const char *displayName);
int32_t WUPSConfigItem_GetDisplayName(WUPSConfigItemHandle handle, char *out_buf, int32_t out_len);
int32_t WUPSConfigItem_SetConfigID(WUPSConfigItemHandle handle, const char *configID);
int32_t WUPSConfigItem_GetConfigID(WUPSConfigItemHandle handle, char *out_buf, int32_t out);
int32_t WUPSConfig_Create(WUPSConfigHandle *out, const char *name);
int32_t WUPSConfigCategory_Destroy(WUPSConfigCategoryHandle handle);
int32_t WUPSConfig_GetName(WUPSConfigHandle handle, char *out_buf, int32_t out_len);
int32_t WUPSConfig_AddCategoryByName(WUPSConfigHandle handle, const char *categoryName, WUPSConfigCategoryHandle *out);
int32_t WUPSConfig_AddCategory(WUPSConfigHandle handle, WUPSConfigCategoryHandle category);
/*
int32_t WUPSConfig_GetCategoryCount(WUPSConfigHandle handle, int32_t *category_count);
int32_t WUPSConfig_GetCategories(WUPSConfigHandle handle, WUPSConfigCategoryHandle *categories_out, int32_t categories_out_size);
*/
int32_t WUPSConfigCategory_Create(WUPSConfigCategoryHandle *out, const char *name);
int32_t WUPSConfigCategory_GetName(WUPSConfigCategoryHandle handle, char *out_buf, int32_t out_len);
int32_t WUPSConfigCategory_AddItem(WUPSConfigCategoryHandle handle, WUPSConfigItemHandle item_Handle);
#define WUPSConfig_AddCategoryByNameHandled(__config__, __categoryName__, __out__) \
do { \
if (WUPSConfig_AddCategoryByName(__config__, __categoryName__, __out__) < 0) { \
WUPSConfig_Destroy(__config__); \
return 0; \
} \
} while (0)
#define WUPSConfig_CreateHandled(__config__, __configName__) \
do { \
if (WUPSConfig_Create(__config__, __configName__) < 0) { \
return 0; \
} \
} while (0)
#ifdef __cplusplus
}
#endif

View File

@ -14,7 +14,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
****************************************************************************/
#pragma once
#include "common.h"
@ -44,18 +43,21 @@ typedef enum wups_loader_hook_type_t {
WUPS_LOADER_HOOK_INIT_WRAPPER, /* Calls __init */
WUPS_LOADER_HOOK_FINI_WRAPPER, /* Calls __fini */
WUPS_LOADER_HOOK_GET_CONFIG,
WUPS_LOADER_HOOK_CONFIG_CLOSED,
WUPS_LOADER_HOOK_GET_CONFIG_DEPRECATED, /* Deprecated implementation */
WUPS_LOADER_HOOK_CONFIG_CLOSED_DEPRECATED, /* Deprecated implementation */
WUPS_LOADER_HOOK_INIT_STORAGE, /* Only for internal usage */
WUPS_LOADER_HOOK_INIT_STORAGE_DEPRECATED, /* Deprecated implementation */
WUPS_LOADER_HOOK_INIT_PLUGIN, /* Called when exiting the plugin loader */
WUPS_LOADER_HOOK_DEINIT_PLUGIN, /* Called when re-entering the plugin loader */
WUPS_LOADER_HOOK_APPLICATION_STARTS, /* Called when an application gets started */
WUPS_LOADER_HOOK_RELEASE_FOREGROUND, /* Called when an foreground is going to be released */
WUPS_LOADER_HOOK_ACQUIRED_FOREGROUND, /* Called when an foreground is acquired */
WUPS_LOADER_HOOK_RELEASE_FOREGROUND, /* Called when a foreground is going to be released */
WUPS_LOADER_HOOK_ACQUIRED_FOREGROUND, /* Called when a foreground is acquired */
WUPS_LOADER_HOOK_APPLICATION_REQUESTS_EXIT, /* Called when an application wants to exit */
WUPS_LOADER_HOOK_APPLICATION_ENDS, /* Called when an application ends */
WUPS_LOADER_HOOK_INIT_STORAGE, /* Only for internal usage */
WUPS_LOADER_HOOK_INIT_CONFIG, /* Only for internal usage */
} wups_loader_hook_type_t;
typedef struct wups_loader_hook_t {
@ -98,30 +100,35 @@ typedef struct wups_loader_hook_t {
WUPS_HOOK_EX(WUPS_LOADER_HOOK_APPLICATION_ENDS, on_app_ending); \
void on_app_ending(void)
#define WUPS_GET_CONFIG() \
WUPSConfigHandle on_get_wups_config(void); \
WUPS_HOOK_EX(WUPS_LOADER_HOOK_GET_CONFIG, on_get_wups_config); \
WUPSConfigHandle on_get_wups_config(void)
#define WUPS_GET_CONFIG(x) \
static_assert(false, "Please use \"WUPSConfigAPI_Init\" inside \"INITIALIZE_PLUGIN\" to provide a callback instead");
#define WUPS_CONFIG_CLOSED() \
void on_wups_config_closed(void); \
WUPS_HOOK_EX(WUPS_LOADER_HOOK_CONFIG_CLOSED, on_wups_config_closed); \
void on_wups_config_closed(void)
#define WUPS_CONFIG_CLOSED() \
static_assert(false, "Please use \"WUPSConfigAPI_Init\" inside \"INITIALIZE_PLUGIN\" to provide a callback instead");
#define WUPS_USE_STORAGE(x) \
WUPS_META(storage_id, x); \
void init_storage(wups_loader_init_storage_args_t); \
WUPS_HOOK_EX(WUPS_LOADER_HOOK_INIT_STORAGE, init_storage); \
void init_storage(wups_loader_init_storage_args_t args) { \
WUPS_InitStorage(args); \
WUPSStorageAPI_InitInternal(args); \
}
#ifdef __cplusplus
#define __EXTERN_C_MACRO extern "C"
#else
#define __EXTERN_C_MACRO
#endif
#define WUPS_INIT_CONFIG_FUNCTIONS() \
__EXTERN_C_MACRO WUPSConfigAPIStatus WUPSConfigAPI_InitLibrary_Internal(wups_loader_init_config_args_t args); \
void wups_init_config_functions(wups_loader_init_config_args_t); \
WUPS_HOOK_EX(WUPS_LOADER_HOOK_INIT_CONFIG, wups_init_config_functions); \
void wups_init_config_functions(wups_loader_init_config_args_t args) { \
WUPSConfigAPI_InitLibrary_Internal(args); \
}
#define WUPS_USE_WUT_MALLOC() \
__EXTERN_C_MACRO void __init_wut_malloc(); \
void on_init_wut_malloc() { \

View File

@ -38,7 +38,7 @@
extern "C" {
#endif
#define WUPS_VERSION_STR "0.7.1"
#define WUPS_VERSION_STR "0.8.0"
#define WUPS_PLUGIN_NAME(__plugin_name) \
WUPS_META(name, __plugin_name); \
WUPS_META(wups, WUPS_VERSION_STR); \
@ -48,14 +48,10 @@ extern "C" {
WUPS_USE_WUT_STDCPP(); \
WUPS___INIT_WRAPPER(); \
WUPS___FINI_WRAPPER(); \
__EXTERN_C_MACRO void abort(); \
__EXTERN_C_MACRO void OSFatal(const char *msg); \
void abort() { \
OSFatal(__plugin_name ": abort() called. Uncaught exception?"); \
while (1) \
; \
} \
WUPS_INIT_CONFIG_FUNCTIONS(); \
WUPS_META(buildtimestamp, __DATE__ " " __TIME__); \
extern const char wups_meta_plugin_name[] WUPS_SECTION("meta"); \
const char wups_meta_plugin_name[] = __plugin_name; \
extern const char wups_meta_info_dump[] WUPS_SECTION("meta"); \
const char wups_meta_info_dump[] = "(plugin: " __plugin_name ";" \
"wups " WUPS_VERSION_STR ";" \

File diff suppressed because it is too large Load Diff

11
include/wups/wups_debug.h Normal file
View File

@ -0,0 +1,11 @@
#pragma once
#ifdef DEBUG
#include <coreinit/debug.h>
#endif
#ifdef DEBUG
#define WUPS_DEBUG_REPORT(fmt, ...) OSReport(fmt, ##__VA_ARGS__)
#else
#define WUPS_DEBUG_REPORT(fmt, ...)
#endif

View File

@ -0,0 +1,67 @@
#include <wups/config/WUPSConfigCategory.h>
#include <wups/config_api.h>
WUPSConfigCategory::WUPSConfigCategory(WUPSConfigCategoryHandle handle) noexcept : mHandle(handle) {
}
WUPSConfigCategory::~WUPSConfigCategory() {
if (mHandle.handle != nullptr) {
WUPSConfigAPI_Category_Destroy(mHandle);
}
}
std::optional<WUPSConfigCategory> WUPSConfigCategory::Create(std::string_view name, WUPSConfigAPIStatus &error) noexcept {
WUPSConfigCategoryHandle catHandle;
if ((error = WUPSConfigAPI_Category_Create({.name = name.data()}, &catHandle)) != WUPSCONFIG_API_RESULT_SUCCESS) {
return std::nullopt;
}
return WUPSConfigCategory(catHandle);
}
WUPSConfigCategory WUPSConfigCategory::Create(std::string_view name) {
WUPSConfigAPIStatus error;
auto res = Create(name, error);
if (!res) {
throw std::runtime_error{std::string("Failed to create category: ").append(name)};
}
return std::move(*res);
}
bool WUPSConfigCategory::add(WUPSConfigCategory &&cat, WUPSConfigAPIStatus &error) noexcept {
if (mHandle.handle == nullptr || cat.getHandle().handle == nullptr) {
OSReport("mHandle %08X item %08X\n", mHandle.handle, cat.getHandle().handle);
return false;
}
if ((error = WUPSConfigAPI_Category_AddCategory(mHandle, cat.getHandle())) != WUPSCONFIG_API_RESULT_SUCCESS) {
return false;
}
cat.release();
return true;
}
void WUPSConfigCategory::add(WUPSConfigCategory &&cat) {
WUPSConfigAPIStatus err;
if (!add(std::move(cat), err)) {
throw std::runtime_error{"Failed to add category to category"};
}
}
bool WUPSConfigCategory::add(WUPSConfigItem &&item, WUPSConfigAPIStatus &error) noexcept {
if (mHandle.handle == nullptr || item.getHandle().handle == nullptr) {
OSReport("mHandle %08X item %08X\n", mHandle.handle, item.getHandle().handle);
error = WUPSCONFIG_API_RESULT_INVALID_ARGUMENT;
return false;
}
if ((error = WUPSConfigAPI_Category_AddItem(mHandle, item.getHandle())) != WUPSCONFIG_API_RESULT_SUCCESS) {
return false;
}
item.release();
return true;
}
void WUPSConfigCategory::add(WUPSConfigItem &&item) {
WUPSConfigAPIStatus err;
if (!add(std::move(item), err)) {
throw std::runtime_error{"Failed to add item to category"};
}
}

View File

@ -0,0 +1,8 @@
#include <wups/config/WUPSConfigItem.h>
#include <wups/config_api.h>
WUPSConfigItem::~WUPSConfigItem() {
if (mHandle.handle != nullptr) {
WUPSConfigAPI_Item_Destroy(mHandle);
}
}

View File

@ -1,46 +1,38 @@
#include "wups/config/WUPSConfigItemBoolean.h"
#include "wups/config_api.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <wups.h>
#include <wups/config.h>
void WUPSConfigItemBoolean_onDelete(void *context);
static void WUPSConfigItemBoolean_onCloseCallback(void *context) {
auto *item = (ConfigItemBoolean *) context;
if (item->valueAtCreation != item->value && item->valueChangedCallback != nullptr) {
((BooleanValueChangedCallback) (item->valueChangedCallback))(item, item->value);
}
}
int32_t WUPSConfigItemBoolean_getCurrentValueDisplay(void *context, char *out_buf, int32_t out_size) {
static inline void toggleValue(ConfigItemBoolean *item) {
item->value = !item->value;
}
static void WUPSConfigItemBoolean_onInput(void *context, WUPSConfigSimplePadData input) {
auto *item = (ConfigItemBoolean *) context;
if ((input.buttons_d & WUPS_CONFIG_BUTTON_A) == WUPS_CONFIG_BUTTON_A) {
toggleValue(item);
} else if (input.buttons_d & WUPS_CONFIG_BUTTON_LEFT && !item->value) {
toggleValue(item);
} else if ((input.buttons_d & WUPS_CONFIG_BUTTON_RIGHT) && item->value) {
toggleValue(item);
}
}
static int32_t WUPSConfigItemBoolean_getCurrentValueDisplay(void *context, char *out_buf, int32_t out_size) {
auto *item = (ConfigItemBoolean *) context;
snprintf(out_buf, out_size, " %s", item->value ? item->trueValue : item->falseValue);
return 0;
}
void toggleValue(ConfigItemBoolean *item) {
item->value = !item->value;
}
bool WUPSConfigItemBoolean_callCallback(void *context) {
auto *item = (ConfigItemBoolean *) context;
if (item->callback != nullptr) {
((BooleanValueChangedCallback) (item->callback))(item, item->value);
return true;
}
return false;
}
void WUPSConfigItemBoolean_onButtonPressed(void *context, WUPSConfigButtons buttons) {
auto *item = (ConfigItemBoolean *) context;
if ((buttons & WUPS_CONFIG_BUTTON_A) == WUPS_CONFIG_BUTTON_A) {
toggleValue(item);
} else if (buttons & WUPS_CONFIG_BUTTON_LEFT && !item->value) {
toggleValue(item);
} else if ((buttons & WUPS_CONFIG_BUTTON_RIGHT) && item->value) {
toggleValue(item);
}
}
bool WUPSConfigItemBoolean_isMovementAllowed(void *context) {
return true;
}
int32_t WUPSConfigItemBoolean_getCurrentValueSelectedDisplay(void *context, char *out_buf, int32_t out_size) {
static int32_t WUPSConfigItemBoolean_getCurrentValueSelectedDisplay(void *context, char *out_buf, int32_t out_size) {
auto *item = (ConfigItemBoolean *) context;
if (item->value) {
snprintf(out_buf, out_size, " %s >", item->trueValue);
@ -50,65 +42,114 @@ int32_t WUPSConfigItemBoolean_getCurrentValueSelectedDisplay(void *context, char
return 0;
}
void WUPSConfigItemBoolean_restoreDefault(void *context) {
static void WUPSConfigItemBoolean_restoreDefault(void *context) {
auto *item = (ConfigItemBoolean *) context;
item->value = item->defaultValue;
}
void WUPSConfigItemBoolean_onSelected(void *context, bool isSelected) {
}
extern "C" bool
WUPSConfigItemBoolean_AddToCategoryEx(WUPSConfigCategoryHandle cat, const char *configId, const char *displayName, bool defaultValue, BooleanValueChangedCallback callback, const char *trueValue,
const char *falseValue) {
if (cat == 0) {
return false;
static void WUPSConfigItemBoolean_Cleanup(ConfigItemBoolean *item) {
if (!item) {
return;
}
auto *item = (ConfigItemBoolean *) malloc(sizeof(ConfigItemBoolean));
if (item == nullptr) {
return false;
}
if (configId != nullptr) {
item->configId = strdup(configId);
} else {
item->configId = nullptr;
}
item->defaultValue = defaultValue;
item->value = defaultValue;
item->callback = (void *) callback;
snprintf(item->trueValue, sizeof(item->trueValue), "%s", trueValue);
snprintf(item->falseValue, sizeof(item->falseValue), "%s", falseValue);
WUPSConfigCallbacks_t callbacks = {
.getCurrentValueDisplay = &WUPSConfigItemBoolean_getCurrentValueDisplay,
.getCurrentValueSelectedDisplay = &WUPSConfigItemBoolean_getCurrentValueSelectedDisplay,
.onSelected = &WUPSConfigItemBoolean_onSelected,
.restoreDefault = &WUPSConfigItemBoolean_restoreDefault,
.isMovementAllowed = &WUPSConfigItemBoolean_isMovementAllowed,
.callCallback = &WUPSConfigItemBoolean_callCallback,
.onButtonPressed = &WUPSConfigItemBoolean_onButtonPressed,
.onDelete = &WUPSConfigItemBoolean_onDelete};
if (WUPSConfigItem_Create(&item->handle, configId, displayName, callbacks, item) < 0) {
free(item);
return false;
}
if (WUPSConfigCategory_AddItem(cat, item->handle) < 0) {
WUPSConfigItem_Destroy(item->handle);
return false;
}
return true;
}
void WUPSConfigItemBoolean_onDelete(void *context) {
auto *item = (ConfigItemBoolean *) context;
free(item->configId);
free((void *) item->identifier);
free(item);
}
extern "C" bool WUPSConfigItemBoolean_AddToCategory(WUPSConfigCategoryHandle cat, const char *configID, const char *displayName, bool defaultValue, BooleanValueChangedCallback callback) {
return WUPSConfigItemBoolean_AddToCategoryEx(cat, configID, displayName, defaultValue, callback, "true", "false");
static void WUPSConfigItemBoolean_onDelete(void *context) {
WUPSConfigItemBoolean_Cleanup((ConfigItemBoolean *) context);
}
extern "C" WUPSConfigAPIStatus
WUPSConfigItemBoolean_CreateEx(const char *identifier,
const char *displayName,
bool defaultValue,
bool currentValue,
BooleanValueChangedCallback callback,
const char *trueValue,
const char *falseValue,
WUPSConfigItemHandle *outHandle) {
if (outHandle == nullptr) {
return WUPSCONFIG_API_RESULT_INVALID_ARGUMENT;
}
auto *item = (ConfigItemBoolean *) malloc(sizeof(ConfigItemBoolean));
if (item == nullptr) {
return WUPSCONFIG_API_RESULT_OUT_OF_MEMORY;
}
if (identifier != nullptr) {
item->identifier = strdup(identifier);
} else {
item->identifier = nullptr;
}
item->defaultValue = defaultValue;
item->value = currentValue;
item->valueAtCreation = currentValue;
item->valueChangedCallback = (void *) callback;
snprintf(item->trueValue, sizeof(item->trueValue), "%s", trueValue);
snprintf(item->falseValue, sizeof(item->falseValue), "%s", falseValue);
WUPSConfigAPIItemCallbacksV2 callbacks = {
.getCurrentValueDisplay = &WUPSConfigItemBoolean_getCurrentValueDisplay,
.getCurrentValueSelectedDisplay = &WUPSConfigItemBoolean_getCurrentValueSelectedDisplay,
.onSelected = nullptr,
.restoreDefault = &WUPSConfigItemBoolean_restoreDefault,
.isMovementAllowed = nullptr,
.onCloseCallback = &WUPSConfigItemBoolean_onCloseCallback,
.onInput = &WUPSConfigItemBoolean_onInput,
.onInputEx = nullptr,
.onDelete = &WUPSConfigItemBoolean_onDelete};
WUPSConfigAPIItemOptionsV2 options = {
.displayName = displayName,
.context = item,
.callbacks = callbacks,
};
WUPSConfigAPIStatus err;
if ((err = WUPSConfigAPI_Item_Create(options, &item->handle)) != WUPSCONFIG_API_RESULT_SUCCESS) {
WUPSConfigItemBoolean_Cleanup(item);
return err;
}
*outHandle = item->handle;
return WUPSCONFIG_API_RESULT_SUCCESS;
}
extern "C" WUPSConfigAPIStatus
WUPSConfigItemBoolean_AddToCategoryEx(WUPSConfigCategoryHandle cat,
const char *identifier,
const char *displayName,
bool defaultValue,
bool currentValue,
BooleanValueChangedCallback callback,
const char *trueValue,
const char *falseValue) {
WUPSConfigItemHandle itemHandle;
WUPSConfigAPIStatus res;
if ((res = WUPSConfigItemBoolean_CreateEx(identifier,
displayName,
defaultValue, currentValue,
callback,
trueValue, falseValue,
&itemHandle)) != WUPSCONFIG_API_RESULT_SUCCESS) {
return res;
}
if ((res = WUPSConfigAPI_Category_AddItem(cat, itemHandle)) != WUPSCONFIG_API_RESULT_SUCCESS) {
WUPSConfigAPI_Item_Destroy(itemHandle);
return res;
}
return WUPSCONFIG_API_RESULT_SUCCESS;
}
extern "C" WUPSConfigAPIStatus
WUPSConfigItemBoolean_AddToCategory(WUPSConfigCategoryHandle cat,
const char *identifier,
const char *displayName,
bool defaultValue,
bool currentValue,
BooleanValueChangedCallback callback) {
return WUPSConfigItemBoolean_AddToCategoryEx(cat, identifier, displayName, defaultValue, currentValue, callback, "true", "false");
}

View File

@ -0,0 +1,36 @@
#include "wups/config/WUPSConfigItemBoolean.h"
std::optional<WUPSConfigItemBoolean> WUPSConfigItemBoolean::CreateEx(std::optional<std::string> identifier, std::string_view displayName, bool defaultValue, bool currentValue, BooleanValueChangedCallback callback, std::string_view trueValue, std::string_view falseValue, WUPSConfigAPIStatus &err) noexcept {
WUPSConfigItemHandle itemHandle;
if ((err = WUPSConfigItemBoolean_CreateEx(identifier ? identifier->data() : nullptr,
displayName.data(),
defaultValue, currentValue,
callback,
trueValue.data(),
falseValue.data(),
&itemHandle)) != WUPSCONFIG_API_RESULT_SUCCESS) {
return std::nullopt;
}
return WUPSConfigItemBoolean(itemHandle);
}
WUPSConfigItemBoolean WUPSConfigItemBoolean::CreateEx(std::optional<std::string> identifier, std::string_view displayName, bool defaultValue, bool currentValue, BooleanValueChangedCallback callback, std::string_view trueValue, std::string_view falseValue) {
WUPSConfigAPIStatus err;
auto result = CreateEx(std::move(identifier), displayName, defaultValue, currentValue, callback, trueValue, falseValue, err);
if (!result) {
throw std::runtime_error(std::string("Failed to create WUPSConfigItemBoolean: ").append(WUPSConfigAPI_GetStatusStr(err)));
}
return std::move(*result);
}
std::optional<WUPSConfigItemBoolean> WUPSConfigItemBoolean::Create(std::optional<std::string> identifier, std::string_view displayName, bool defaultValue, bool currentValue, BooleanValueChangedCallback callback, WUPSConfigAPIStatus &err) noexcept {
return CreateEx(std::move(identifier), displayName, defaultValue, currentValue, callback, "true", "false", err);
}
WUPSConfigItemBoolean WUPSConfigItemBoolean::Create(std::optional<std::string> identifier, std::string_view displayName, bool defaultValue, bool currentValue, BooleanValueChangedCallback callback) {
WUPSConfigAPIStatus err = WUPSCONFIG_API_RESULT_UNKNOWN_ERROR;
auto res = Create(std::move(identifier), displayName, defaultValue, currentValue, callback, err);
if (!res) {
throw std::runtime_error(std::string("Failed to create WUPSConfigItemBoolean: ").append(WUPSConfigAPI_GetStatusStr(err)));
}
return std::move(*res);
}

View File

@ -1,33 +1,27 @@
#include "wups/config/WUPSConfigItemIntegerRange.h"
#include "wups/config_api.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <wups.h>
#include <wups/config.h>
int32_t WUPSConfigItemIntegerRange_getCurrentValueDisplay(void *context, char *out_buf, int32_t out_size) {
void WUPSConfigItemIntegerRange_onCloseCallback(void *context) {
auto *item = (ConfigItemIntegerRange *) context;
snprintf(out_buf, out_size, "%d", item->value);
return 0;
}
bool WUPSConfigItemIntegerRange_callCallback(void *context) {
auto *item = (ConfigItemIntegerRange *) context;
if (item->callback != nullptr) {
((IntegerRangeValueChangedCallback) item->callback)(item, item->value);
return true;
if (item->valueAtCreation != item->value && item->valueChangedCallback != nullptr) {
((IntegerRangeValueChangedCallback) item->valueChangedCallback)(item, item->value);
}
return false;
}
void WUPSConfigItemIntegerRange_onButtonPressed(void *context, WUPSConfigButtons buttons) {
void WUPSConfigItemIntegerRange_onInput(void *context, WUPSConfigSimplePadData input) {
auto *item = (ConfigItemIntegerRange *) context;
if (buttons & WUPS_CONFIG_BUTTON_LEFT) {
if (input.buttons_d & WUPS_CONFIG_BUTTON_LEFT) {
item->value--;
} else if ((buttons & WUPS_CONFIG_BUTTON_RIGHT)) {
} else if ((input.buttons_d & WUPS_CONFIG_BUTTON_RIGHT)) {
item->value++;
} else if ((buttons & WUPS_CONFIG_BUTTON_L)) {
} else if ((input.buttons_d & WUPS_CONFIG_BUTTON_L)) {
item->value = item->value - 50;
} else if ((buttons & WUPS_CONFIG_BUTTON_R)) {
} else if ((input.buttons_d & WUPS_CONFIG_BUTTON_R)) {
item->value = item->value + 50;
}
@ -38,8 +32,10 @@ void WUPSConfigItemIntegerRange_onButtonPressed(void *context, WUPSConfigButtons
}
}
bool WUPSConfigItemIntegerRange_isMovementAllowed(void *context) {
return true;
int32_t WUPSConfigItemIntegerRange_getCurrentValueDisplay(void *context, char *out_buf, int32_t out_size) {
auto *item = (ConfigItemIntegerRange *) context;
snprintf(out_buf, out_size, " %d", item->value);
return 0;
}
int32_t WUPSConfigItemIntegerRange_getCurrentValueSelectedDisplay(void *context, char *out_buf, int32_t out_size) {
@ -59,55 +55,99 @@ void WUPSConfigItemIntegerRange_restoreDefault(void *context) {
item->value = item->defaultValue;
}
void WUPSConfigItemIntegerRange_onDelete(void *context) {
auto *item = (ConfigItemIntegerRange *) context;
free(item->configId);
static void WUPSConfigItemIntegerRange_Cleanup(ConfigItemIntegerRange *item) {
if (!item) {
return;
}
free((void *) item->identifier);
free(item);
}
void WUPSConfigItemIntegerRange_onSelected(void *context, bool isSelected) {
void WUPSConfigItemIntegerRange_onDelete(void *context) {
WUPSConfigItemIntegerRange_Cleanup((ConfigItemIntegerRange *) context);
}
extern "C" bool WUPSConfigItemIntegerRange_AddToCategory(WUPSConfigCategoryHandle cat, const char *configId, const char *displayName, int32_t defaultValue, int32_t minValue, int32_t maxValue,
IntegerRangeValueChangedCallback callback) {
if (cat == 0) {
return false;
extern "C" WUPSConfigAPIStatus
WUPSConfigItemIntegerRange_Create(const char *identifier,
const char *displayName,
int32_t defaultValue, int32_t currentValue,
int32_t minValue, int32_t maxValue,
IntegerRangeValueChangedCallback callback,
WUPSConfigItemHandle *outHandle) {
if (outHandle == nullptr) {
return WUPSCONFIG_API_RESULT_INVALID_ARGUMENT;
}
if (maxValue < minValue || defaultValue < minValue || defaultValue > maxValue || currentValue < minValue || currentValue > maxValue) {
return WUPSCONFIG_API_RESULT_INVALID_ARGUMENT;
}
*outHandle = {};
auto *item = (ConfigItemIntegerRange *) malloc(sizeof(ConfigItemIntegerRange));
if (item == nullptr) {
return false;
return WUPSCONFIG_API_RESULT_OUT_OF_MEMORY;
}
if (configId != nullptr) {
item->configId = strdup(configId);
if (identifier != nullptr) {
item->identifier = strdup(identifier);
} else {
item->configId = nullptr;
item->identifier = nullptr;
}
item->defaultValue = defaultValue;
item->value = defaultValue;
item->minValue = minValue;
item->maxValue = maxValue;
item->callback = (void *) callback;
item->defaultValue = defaultValue;
item->value = currentValue;
item->valueAtCreation = currentValue;
item->minValue = minValue;
item->maxValue = maxValue;
item->valueChangedCallback = (void *) callback;
WUPSConfigCallbacks_t callbacks = {
WUPSConfigAPIItemCallbacksV2 callbacks = {
.getCurrentValueDisplay = &WUPSConfigItemIntegerRange_getCurrentValueDisplay,
.getCurrentValueSelectedDisplay = &WUPSConfigItemIntegerRange_getCurrentValueSelectedDisplay,
.onSelected = &WUPSConfigItemIntegerRange_onSelected,
.onSelected = nullptr,
.restoreDefault = &WUPSConfigItemIntegerRange_restoreDefault,
.isMovementAllowed = &WUPSConfigItemIntegerRange_isMovementAllowed,
.callCallback = &WUPSConfigItemIntegerRange_callCallback,
.onButtonPressed = &WUPSConfigItemIntegerRange_onButtonPressed,
.onDelete = &WUPSConfigItemIntegerRange_onDelete};
if (WUPSConfigItem_Create(&(item->handle), configId, displayName, callbacks, item) < 0) {
free(item);
return false;
.isMovementAllowed = nullptr,
.onCloseCallback = &WUPSConfigItemIntegerRange_onCloseCallback,
.onInput = &WUPSConfigItemIntegerRange_onInput,
.onInputEx = nullptr,
.onDelete = &WUPSConfigItemIntegerRange_onDelete,
};
if (WUPSConfigCategory_AddItem(cat, item->handle) < 0) {
WUPSConfigItem_Destroy(item->handle);
return false;
WUPSConfigAPIItemOptionsV2 options = {
.displayName = displayName,
.context = item,
.callbacks = callbacks,
};
WUPSConfigAPIStatus err;
if ((err = WUPSConfigAPI_Item_Create(options, &(item->handle))) != WUPSCONFIG_API_RESULT_SUCCESS) {
WUPSConfigItemIntegerRange_Cleanup(item);
return err;
}
return true;
}
*outHandle = item->handle;
return WUPSCONFIG_API_RESULT_SUCCESS;
}
extern "C" WUPSConfigAPIStatus
WUPSConfigItemIntegerRange_AddToCategory(WUPSConfigCategoryHandle cat,
const char *identifier,
const char *displayName,
int32_t defaultValue, int32_t currentValue,
int32_t minValue, int32_t maxValue,
IntegerRangeValueChangedCallback callback) {
WUPSConfigItemHandle itemHandle;
WUPSConfigAPIStatus res;
if ((res = WUPSConfigItemIntegerRange_Create(identifier,
displayName,
defaultValue, currentValue,
minValue, maxValue,
callback,
&itemHandle)) != WUPSCONFIG_API_RESULT_SUCCESS) {
return res;
}
if ((res = WUPSConfigAPI_Category_AddItem(cat, itemHandle)) != WUPSCONFIG_API_RESULT_SUCCESS) {
WUPSConfigAPI_Item_Destroy(itemHandle);
return res;
}
return WUPSCONFIG_API_RESULT_SUCCESS;
}

View File

@ -0,0 +1,34 @@
#include "wups/config/WUPSConfigItemIntegerRange.h"
std::optional<WUPSConfigItemIntegerRange> WUPSConfigItemIntegerRange::Create(
std::optional<std::string> identifier,
std::string_view displayName,
int32_t defaultValue, int32_t currentValue,
int32_t minValue, int32_t maxValue,
IntegerRangeValueChangedCallback valuesChangedCallback,
WUPSConfigAPIStatus &err) noexcept {
WUPSConfigItemHandle itemHandle;
if ((err = WUPSConfigItemIntegerRange_Create(identifier ? identifier->c_str() : nullptr,
displayName.data(),
defaultValue, currentValue,
minValue, maxValue,
valuesChangedCallback,
&itemHandle)) != WUPSCONFIG_API_RESULT_SUCCESS) {
return std::nullopt;
}
return WUPSConfigItemIntegerRange(itemHandle);
}
WUPSConfigItemIntegerRange WUPSConfigItemIntegerRange::Create(
std::optional<std::string> identifier,
std::string_view displayName,
int32_t defaultValue, int32_t currentValue,
int32_t minValue, int32_t maxValue,
IntegerRangeValueChangedCallback valuesChangedCallback) {
WUPSConfigAPIStatus err = WUPSCONFIG_API_RESULT_UNKNOWN_ERROR;
auto result = Create(std::move(identifier), displayName, defaultValue, currentValue, minValue, maxValue, valuesChangedCallback, err);
if (!result) {
throw std::runtime_error(std::string("Failed to create WUPSConfigItemIntegerRange: ").append(WUPSConfigAPI_GetStatusStr(err)));
}
return std::move(*result);
}

View File

@ -1,12 +1,11 @@
#include "wups/config/WUPSConfigItemMultipleValues.h"
#include "wups/config_api.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <wups.h>
#include <wups/config.h>
void WUPSConfigItemMultipleValues_onDelete(void *context);
int32_t WUPSConfigItemMultipleValues_getCurrentValueDisplay(void *context, char *out_buf, int32_t out_size) {
static int32_t WUPSConfigItemMultipleValues_getCurrentValueDisplay(void *context, char *out_buf, int32_t out_size) {
auto *item = (ConfigItemMultipleValues *) context;
if (item->values && item->valueIndex >= 0 && item->valueIndex < item->valueCount) {
@ -19,20 +18,20 @@ int32_t WUPSConfigItemMultipleValues_getCurrentValueDisplay(void *context, char
return -1;
}
bool WUPSConfigItemMultipleValues_callCallback(void *context) {
static void WUPSConfigItemMultipleValues_onCloseCallback(void *context) {
auto *item = (ConfigItemMultipleValues *) context;
if (item->callback != nullptr && item->values && item->valueIndex >= 0 && item->valueIndex < item->valueCount) {
((MultipleValuesChangedCallback) (item->callback))(item, item->values[item->valueIndex].value);
return true;
if (item->valueIndexAtCreation != item->valueIndex && item->valueChangedCallback != nullptr) {
if (item->values && item->valueIndex >= 0 && item->valueIndex < item->valueCount) {
((MultipleValuesChangedCallback) (item->valueChangedCallback))(item, item->values[item->valueIndex].value);
}
}
return false;
}
void WUPSConfigItemMultipleValues_onButtonPressed(void *context, WUPSConfigButtons buttons) {
static void WUPSConfigItemMultipleValues_onInput(void *context, WUPSConfigSimplePadData input) {
auto *item = (ConfigItemMultipleValues *) context;
if (buttons & WUPS_CONFIG_BUTTON_LEFT) {
if (input.buttons_d & WUPS_CONFIG_BUTTON_LEFT) {
item->valueIndex--;
} else if (buttons & WUPS_CONFIG_BUTTON_RIGHT) {
} else if (input.buttons_d & WUPS_CONFIG_BUTTON_RIGHT) {
item->valueIndex++;
}
if (item->valueIndex < 0) {
@ -42,11 +41,7 @@ void WUPSConfigItemMultipleValues_onButtonPressed(void *context, WUPSConfigButto
}
}
bool WUPSConfigItemMultipleValues_isMovementAllowed(void *context) {
return true;
}
int32_t WUPSConfigItemMultipleValues_getCurrentValueSelectedDisplay(void *context, char *out_buf, int32_t out_size) {
static int32_t WUPSConfigItemMultipleValues_getCurrentValueSelectedDisplay(void *context, char *out_buf, int32_t out_size) {
auto *item = (ConfigItemMultipleValues *) context;
if (item->values && item->valueIndex >= 0 && item->valueIndex < item->valueCount) {
if (item->valueCount == 1) {
@ -64,82 +59,119 @@ int32_t WUPSConfigItemMultipleValues_getCurrentValueSelectedDisplay(void *contex
return 0;
}
void WUPSConfigItemMultipleValues_restoreDefault(void *context) {
static void WUPSConfigItemMultipleValues_restoreDefault(void *context) {
auto *item = (ConfigItemMultipleValues *) context;
item->valueIndex = item->defaultValueIndex;
}
void WUPSConfigItemMultipleValues_onSelected(void *context, bool isSelected) {
}
extern "C" bool
WUPSConfigItemMultipleValues_AddToCategory(WUPSConfigCategoryHandle cat, const char *configId, const char *displayName,
int32_t defaultValueIndex, ConfigItemMultipleValuesPair *possibleValues,
int pairCount, MultipleValuesChangedCallback callback) {
if (cat == 0 || displayName == nullptr || possibleValues == nullptr || pairCount < 0) {
return false;
static void WUPSConfigItemMultipleValues_Cleanup(ConfigItemMultipleValues *item) {
if (!item) {
return;
}
auto *item = (ConfigItemMultipleValues *) malloc(sizeof(ConfigItemMultipleValues));
if (item == nullptr) {
return false;
}
auto *values = (ConfigItemMultipleValuesPair *) malloc(sizeof(ConfigItemMultipleValuesPair) * pairCount);
for (int i = 0; i < pairCount; ++i) {
values[i].value = possibleValues[i].value;
if (possibleValues[i].valueName == nullptr) {
values[i].valueName = nullptr;
continue;
}
auto bufLen = strlen(possibleValues[i].valueName) + 1;
values[i].valueName = (char *) malloc(bufLen);
strncpy(values[i].valueName, possibleValues[i].valueName, bufLen);
}
item->valueCount = pairCount;
item->values = values;
item->valueIndex = defaultValueIndex;
item->defaultValueIndex = defaultValueIndex;
item->callback = (void *) callback;
if (configId != nullptr) {
item->configId = strdup(configId);
} else {
item->configId = nullptr;
}
WUPSConfigCallbacks_t callbacks = {
.getCurrentValueDisplay = &WUPSConfigItemMultipleValues_getCurrentValueDisplay,
.getCurrentValueSelectedDisplay = &WUPSConfigItemMultipleValues_getCurrentValueSelectedDisplay,
.onSelected = &WUPSConfigItemMultipleValues_onSelected,
.restoreDefault = &WUPSConfigItemMultipleValues_restoreDefault,
.isMovementAllowed = &WUPSConfigItemMultipleValues_isMovementAllowed,
.callCallback = &WUPSConfigItemMultipleValues_callCallback,
.onButtonPressed = &WUPSConfigItemMultipleValues_onButtonPressed,
.onDelete = &WUPSConfigItemMultipleValues_onDelete};
if (WUPSConfigItem_Create(&item->handle, configId, displayName, callbacks, item) < 0) {
free(item);
return false;
}
if (WUPSConfigCategory_AddItem(cat, item->handle) < 0) {
WUPSConfigItem_Destroy(item->handle);
return false;
}
return true;
}
void WUPSConfigItemMultipleValues_onDelete(void *context) {
auto *item = (ConfigItemMultipleValues *) context;
for (int i = 0; i < item->valueCount; ++i) {
free(item->values[i].valueName);
free((void *) item->values[i].valueName);
}
free(item->configId);
free(item->values);
free((void *) item->identifier);
free(item->values);
free(item);
}
}
static void WUPSConfigItemMultipleValues_onDelete(void *context) {
auto *item = (ConfigItemMultipleValues *) context;
WUPSConfigItemMultipleValues_Cleanup(item);
}
extern "C" WUPSConfigAPIStatus
WUPSConfigItemMultipleValues_Create(const char *identifier, const char *displayName,
int32_t defaultValueIndex, int currentValueIndex,
ConfigItemMultipleValuesPair *possibleValues,
int pairCount, MultipleValuesChangedCallback callback,
WUPSConfigItemHandle *outHandle) {
if (outHandle == nullptr || displayName == nullptr || possibleValues == nullptr || pairCount < 0 ||
defaultValueIndex >= pairCount || currentValueIndex >= pairCount) {
return WUPSCONFIG_API_RESULT_INVALID_ARGUMENT;
}
*outHandle = {};
auto *item = (ConfigItemMultipleValues *) malloc(sizeof(ConfigItemMultipleValues));
if (item == nullptr) {
return WUPSCONFIG_API_RESULT_OUT_OF_MEMORY;
}
*item = {};
item->values = (ConfigItemMultipleValuesPair *) malloc(sizeof(ConfigItemMultipleValuesPair) * pairCount);
if (!item->values) {
WUPSConfigItemMultipleValues_Cleanup(item);
return WUPSCONFIG_API_RESULT_OUT_OF_MEMORY;
}
if (identifier != nullptr) {
item->identifier = strdup(identifier);
} else {
item->identifier = nullptr;
}
for (int i = 0; i < pairCount; ++i) {
item->values[i].value = possibleValues[i].value;
if (possibleValues[i].valueName == nullptr) {
item->values[i].valueName = nullptr;
continue;
}
item->values[i].valueName = strdup(possibleValues[i].valueName);
}
item->valueCount = pairCount;
item->defaultValueIndex = defaultValueIndex;
item->valueIndex = currentValueIndex;
item->valueIndexAtCreation = currentValueIndex;
item->valueChangedCallback = (void *) callback;
WUPSConfigAPIItemCallbacksV2 callbacks = {
.getCurrentValueDisplay = &WUPSConfigItemMultipleValues_getCurrentValueDisplay,
.getCurrentValueSelectedDisplay = &WUPSConfigItemMultipleValues_getCurrentValueSelectedDisplay,
.onSelected = nullptr,
.restoreDefault = &WUPSConfigItemMultipleValues_restoreDefault,
.isMovementAllowed = nullptr,
.onCloseCallback = &WUPSConfigItemMultipleValues_onCloseCallback,
.onInput = &WUPSConfigItemMultipleValues_onInput,
.onInputEx = nullptr,
.onDelete = &WUPSConfigItemMultipleValues_onDelete};
WUPSConfigAPIItemOptionsV2 options = {
.displayName = displayName,
.context = item,
.callbacks = callbacks,
};
WUPSConfigAPIStatus err;
if ((err = WUPSConfigAPI_Item_Create(options, &item->handle)) != WUPSCONFIG_API_RESULT_SUCCESS) {
WUPSConfigItemMultipleValues_Cleanup(item);
return err;
}
*outHandle = item->handle;
return WUPSCONFIG_API_RESULT_SUCCESS;
}
extern "C" WUPSConfigAPIStatus
WUPSConfigItemMultipleValues_AddToCategory(WUPSConfigCategoryHandle cat, const char *identifier, const char *displayName,
int32_t defaultValueIndex, int currentValueIndex,
ConfigItemMultipleValuesPair *possibleValues, int pairCount,
MultipleValuesChangedCallback callback) {
WUPSConfigItemHandle itemHandle;
WUPSConfigAPIStatus res;
if ((res = WUPSConfigItemMultipleValues_Create(identifier,
displayName,
defaultValueIndex, currentValueIndex,
possibleValues, pairCount,
callback,
&itemHandle)) != WUPSCONFIG_API_RESULT_SUCCESS) {
return res;
}
if ((res = WUPSConfigAPI_Category_AddItem(cat, itemHandle)) != WUPSCONFIG_API_RESULT_SUCCESS) {
WUPSConfigAPI_Item_Destroy(itemHandle);
return res;
}
return WUPSCONFIG_API_RESULT_SUCCESS;
}

View File

@ -0,0 +1,102 @@
#include "wups/config/WUPSConfigItemMultipleValues.h"
std::optional<WUPSConfigItemMultipleValues> WUPSConfigItemMultipleValues::CreateFromIndex(
std::optional<const std::string> identifier,
std::string_view displayName,
int defaultValueIndex, int currentValueIndex,
const std::span<const ValuePair> &possibleValues,
MultipleValuesChangedCallback valuesChangedCallback,
WUPSConfigAPIStatus &err) noexcept {
auto *values = (ConfigItemMultipleValuesPair *) malloc(possibleValues.size_bytes());
if (!values) {
err = WUPSCONFIG_API_RESULT_OUT_OF_MEMORY;
return std::nullopt;
}
int i = 0;
for (const auto &cur : possibleValues) {
values[i].value = cur.value;
values[i].valueName = cur.name.data();
i++;
}
WUPSConfigItemHandle itemHandle;
err = WUPSConfigItemMultipleValues_Create(
identifier ? identifier->c_str() : nullptr,
displayName.data(),
defaultValueIndex, currentValueIndex,
values, (int32_t) possibleValues.size(),
valuesChangedCallback,
&itemHandle);
free(values);
if (err != WUPSCONFIG_API_RESULT_SUCCESS) {
return std::nullopt;
}
return WUPSConfigItemMultipleValues(itemHandle);
}
WUPSConfigItemMultipleValues WUPSConfigItemMultipleValues::CreateFromIndex(
std::optional<const std::string> identifier,
std::string_view displayName,
int defaultValueIndex, int currentValueIndex,
const std::span<const ValuePair> &possibleValues,
MultipleValuesChangedCallback valuesChangedCallback) {
WUPSConfigAPIStatus err = WUPSCONFIG_API_RESULT_UNKNOWN_ERROR;
auto result = CreateFromIndex(std::move(identifier), displayName, defaultValueIndex, currentValueIndex, possibleValues, valuesChangedCallback, err);
if (!result) {
throw std::runtime_error(std::string("Failed to create WUPSConfigItemMultipleValues: ").append(WUPSConfigAPI_GetStatusStr(err)));
}
return std::move(*result);
}
std::optional<WUPSConfigItemMultipleValues> WUPSConfigItemMultipleValues::CreateFromValue(
std::optional<const std::string> identifier,
std::string_view displayName,
uint32_t defaultValue, uint32_t currentValue,
const std::span<const ValuePair> &possibleValues,
MultipleValuesChangedCallback valuesChangedCallback,
WUPSConfigAPIStatus &err) noexcept {
int defaultIndex = -1;
int currentValueIndex = -1;
int i = 0;
for (const auto &cur : possibleValues) {
if (defaultIndex != -1 && currentValueIndex != -1) {
break;
}
if (cur.value == currentValue) {
currentValueIndex = i;
}
if (cur.value == defaultValue) {
defaultIndex = i;
}
i++;
}
if (defaultIndex == -1 || currentValueIndex == -1) {
err = WUPSCONFIG_API_RESULT_INVALID_ARGUMENT;
return std::nullopt;
}
return WUPSConfigItemMultipleValues::CreateFromIndex(std::move(identifier),
displayName,
defaultIndex, currentValueIndex,
possibleValues,
valuesChangedCallback,
err);
}
WUPSConfigItemMultipleValues WUPSConfigItemMultipleValues::CreateFromValue(
std::optional<const std::string> identifier, std::string_view displayName,
int32_t defaultValue, int32_t currentValue,
const std::span<const ValuePair> &possibleValues,
MultipleValuesChangedCallback valuesChangedCallback) {
WUPSConfigAPIStatus err = WUPSCONFIG_API_RESULT_UNKNOWN_ERROR;
auto result = CreateFromValue(std::move(identifier),
displayName,
defaultValue, currentValue,
possibleValues,
valuesChangedCallback,
err);
if (!result) {
throw std::runtime_error(std::string("Failed to create WUPSConfigItemMultipleValues (\"").append(displayName).append("\":").append(WUPSConfigAPI_GetStatusStr(err)));
}
return std::move(*result);
}

View File

@ -1,76 +1,69 @@
#include "wups/config/WUPSConfigItemStub.h"
#include <cstdio>
#include "wups/config_api.h"
#include <cstdlib>
#include <cstring>
#include <wups.h>
#include <wups/config.h>
void WUPSConfigItemStub_onDelete(void *context);
int32_t WUPSConfigItemStub_getCurrentValueDisplay(void *context, char *out_buf, int32_t out_size) {
static int32_t WUPSConfigItemStub_getEmptyTextValue(void *context, char *out_buf, int32_t out_size) {
memset(out_buf, 0, out_size);
return 0;
}
bool WUPSConfigItemStub_callCallback(void *context) {
return false;
static void WUPSConfigItemStub_Cleanup(void *item) {
free(item);
}
void WUPSConfigItemStub_onButtonPressed(void *context, WUPSConfigButtons buttons) {
}
bool WUPSConfigItemStub_isMovementAllowed(void *context) {
return true;
}
int32_t WUPSConfigItemStub_getCurrentValueSelectedDisplay(void *context, char *out_buf, int32_t out_size) {
memset(out_buf, 0, out_size);
return 0;
}
void WUPSConfigItemStub_restoreDefault(void *context) {
}
void WUPSConfigItemStub_onSelected(void *context, bool isSelected) {
}
extern "C" bool
WUPSConfigItemStub_AddToCategoryEx(WUPSConfigCategoryHandle cat, const char *configID, const char *displayName) {
if (cat == 0) {
return false;
extern "C" WUPSConfigAPIStatus
WUPSConfigItemStub_Create(const char *displayName, WUPSConfigItemHandle *outHandle) {
if (displayName == nullptr || outHandle == nullptr) {
return WUPSCONFIG_API_RESULT_INVALID_ARGUMENT;
}
auto *item = (ConfigItemStub *) malloc(sizeof(ConfigItemStub));
if (item == nullptr) {
return false;
return WUPSCONFIG_API_RESULT_OUT_OF_MEMORY;
}
WUPSConfigCallbacks_t callbacks = {
.getCurrentValueDisplay = &WUPSConfigItemStub_getCurrentValueDisplay,
.getCurrentValueSelectedDisplay = &WUPSConfigItemStub_getCurrentValueSelectedDisplay,
.onSelected = &WUPSConfigItemStub_onSelected,
.restoreDefault = &WUPSConfigItemStub_restoreDefault,
.isMovementAllowed = &WUPSConfigItemStub_isMovementAllowed,
.callCallback = &WUPSConfigItemStub_callCallback,
.onButtonPressed = &WUPSConfigItemStub_onButtonPressed,
.onDelete = &WUPSConfigItemStub_onDelete};
WUPSConfigAPIItemCallbacksV2 callbacks = {
.getCurrentValueDisplay = &WUPSConfigItemStub_getEmptyTextValue,
.getCurrentValueSelectedDisplay = &WUPSConfigItemStub_getEmptyTextValue,
.onSelected = nullptr,
.restoreDefault = nullptr,
.isMovementAllowed = nullptr,
.onCloseCallback = nullptr,
.onInput = nullptr,
.onInputEx = nullptr,
.onDelete = &WUPSConfigItemStub_Cleanup,
};
if (WUPSConfigItem_Create(&item->handle, configID, displayName, callbacks, item) < 0) {
free(item);
return false;
WUPSConfigAPIItemOptionsV2 options = {
.displayName = displayName,
.context = item,
.callbacks = callbacks,
};
WUPSConfigAPIStatus err;
if ((err = WUPSConfigAPI_Item_Create(options, &item->handle)) != WUPSCONFIG_API_RESULT_SUCCESS) {
WUPSConfigItemStub_Cleanup(item);
return err;
}
if (WUPSConfigCategory_AddItem(cat, item->handle) < 0) {
WUPSConfigItem_Destroy(item->handle);
return false;
*outHandle = item->handle;
return WUPSCONFIG_API_RESULT_SUCCESS;
}
extern "C" WUPSConfigAPIStatus
WUPSConfigItemStub_AddToCategory(WUPSConfigCategoryHandle cat, const char *displayName) {
WUPSConfigItemHandle itemHandle;
WUPSConfigAPIStatus res;
if ((res = WUPSConfigItemStub_Create(displayName,
&itemHandle)) != WUPSCONFIG_API_RESULT_SUCCESS) {
return res;
}
return true;
}
void WUPSConfigItemStub_onDelete(void *context) {
auto *item = (ConfigItemStub *) context;
free(item);
}
extern "C" bool WUPSConfigItemStub_AddToCategory(WUPSConfigCategoryHandle cat, const char *configID, const char *displayName) {
return WUPSConfigItemStub_AddToCategoryEx(cat, configID, displayName);
}
if ((res = WUPSConfigAPI_Category_AddItem(cat, itemHandle)) != WUPSCONFIG_API_RESULT_SUCCESS) {
WUPSConfigAPI_Item_Destroy(itemHandle);
return res;
}
return WUPSCONFIG_API_RESULT_SUCCESS;
}

View File

@ -0,0 +1,20 @@
#include "wups/config/WUPSConfigItemStub.h"
std::optional<WUPSConfigItemStub> WUPSConfigItemStub::Create(std::string_view displayName,
WUPSConfigAPIStatus &err) noexcept {
WUPSConfigItemHandle itemHandle;
if ((err = WUPSConfigItemStub_Create(displayName.data(),
&itemHandle)) != WUPSCONFIG_API_RESULT_SUCCESS) {
return std::nullopt;
}
return WUPSConfigItemStub(itemHandle);
}
WUPSConfigItemStub WUPSConfigItemStub::Create(std::string_view displayName) {
WUPSConfigAPIStatus err = WUPSCONFIG_API_RESULT_UNKNOWN_ERROR;
auto result = Create(displayName, err);
if (!result) {
throw std::runtime_error(std::string("Failed to create WUPSConfigItemStub: ").append(WUPSConfigAPI_GetStatusStr(err)));
}
return std::move(*result);
}

View File

@ -1,20 +0,0 @@
:NAME homebrew_wupsbackend
:TEXT
WUPSConfigItem_Create
WUPSConfigItem_Destroy
WUPSConfigItem_SetDisplayName
WUPSConfigItem_GetDisplayName
WUPSConfigItem_SetConfigID
WUPSConfigItem_GetConfigID
WUPSConfig_Create
WUPSConfig_Destroy
WUPSConfig_GetName
WUPSConfig_AddCategoryByName
WUPSConfig_AddCategory
WUPSConfigCategory_Create
WUPSConfigCategory_Destroy
WUPSConfigCategory_GetName
WUPSConfigCategory_AddItem

View File

@ -0,0 +1,245 @@
#include "wups/config_api.h"
#include "wups/wups_debug.h"
#include <coreinit/dynload.h>
#include <wups/config.h>
static OSDynLoad_Module sModuleHandle = nullptr;
static WUPSConfigAPIStatus (*sAPIGetVersion)(WUPSConfigAPIVersion *out) = nullptr;
static WUPSConfigAPIStatus (*sAPIInitEx)(uint32_t pluginIdentifier, WUPSConfigAPIOptions options, WUPSConfigAPI_MenuOpenedCallback openedCallback, WUPSConfigAPI_MenuClosedCallback closedCallback) = nullptr;
static WUPSConfigAPIStatus (*sAPICategoryCreateEx)(WUPSConfigAPICreateCategoryOptions options, WUPSConfigCategoryHandle *out) = nullptr;
static WUPSConfigAPIStatus (*sAPICategoryDestroy)(WUPSConfigCategoryHandle handle) = nullptr;
static WUPSConfigAPIStatus (*sAPICategoryAddCategory)(WUPSConfigCategoryHandle parentHandle, WUPSConfigCategoryHandle categoryHandle) = nullptr;
static WUPSConfigAPIStatus (*sAPICategoryAddItem)(WUPSConfigCategoryHandle parentHandle, WUPSConfigItemHandle itemHandle) = nullptr;
static WUPSConfigAPIStatus (*sAPIItemCreateEx)(WUPSConfigAPICreateItemOptions options, WUPSConfigItemHandle *out) = nullptr;
static WUPSConfigAPIStatus (*sAPIItemDestroy)(WUPSConfigItemHandle handle) = nullptr;
static WUPSConfigAPIVersion sConfigAPIVersion = WUPS_CONFIG_API_VERSION_ERROR;
static bool sConfigLibInitDone = false;
static uint32_t sConfigPluginIdentifier = 0xFFFFFFFF;
const char *WUPSConfigAPI_GetStatusStr(WUPSConfigAPIStatus status) {
switch (status) {
case WUPSCONFIG_API_RESULT_SUCCESS:
return "WUPSCONFIG_API_RESULT_SUCCESS";
case WUPSCONFIG_API_RESULT_INVALID_ARGUMENT:
return "WUPSCONFIG_API_RESULT_INVALID_ARGUMENT";
case WUPSCONFIG_API_RESULT_OUT_OF_MEMORY:
return "WUPSCONFIG_API_RESULT_OUT_OF_MEMORY";
case WUPSCONFIG_API_RESULT_NOT_FOUND:
return "WUPSCONFIG_API_RESULT_NOT_FOUND";
case WUPSCONFIG_API_RESULT_MODULE_NOT_FOUND:
return "WUPSCONFIG_API_RESULT_MODULE_NOT_FOUND";
case WUPSCONFIG_API_RESULT_INVALID_PLUGIN_IDENTIFIER:
return "WUPSCONFIG_API_RESULT_INVALID_PLUGIN_IDENTIFIER";
case WUPSCONFIG_API_RESULT_MODULE_MISSING_EXPORT:
return "WUPSCONFIG_API_RESULT_MODULE_MISSING_EXPORT";
case WUPSCONFIG_API_RESULT_UNSUPPORTED_VERSION:
return "WUPSCONFIG_API_RESULT_UNSUPPORTED_VERSION";
case WUPSCONFIG_API_RESULT_UNSUPPORTED_COMMAND:
return "WUPSCONFIG_API_RESULT_UNSUPPORTED_COMMAND";
case WUPSCONFIG_API_RESULT_LIB_UNINITIALIZED:
return "WUPSCONFIG_API_RESULT_LIB_UNINITIALIZED";
case WUPSCONFIG_API_RESULT_UNKNOWN_ERROR:
return "WUPSCONFIG_API_RESULT_UNKNOWN_ERROR";
case WUPSCONFIG_API_RESULT_MISSING_CALLBACK:
return "WUPSCONFIG_API_RESULT_MISSING_CALLBACK";
}
return "WUPSCONFIG_API_RESULT_UNKNOWN_ERROR";
}
extern "C" WUPSConfigAPIStatus WUPSConfigAPI_InitLibrary_Internal(wups_loader_init_config_args_t args) {
if (sConfigLibInitDone) {
return WUPSCONFIG_API_RESULT_SUCCESS;
}
if (args.arg_version != 1) {
return WUPSCONFIG_API_RESULT_UNSUPPORTED_VERSION;
}
if (OSDynLoad_Acquire("homebrew_wupsbackend", &sModuleHandle) != OS_DYNLOAD_OK) {
WUPS_DEBUG_REPORT("libuwps: OSDynLoad_Acquire \"homebrew_wupsbackend\" failed.\n");
return WUPSCONFIG_API_RESULT_MODULE_NOT_FOUND;
}
if (OSDynLoad_FindExport(sModuleHandle, OS_DYNLOAD_EXPORT_FUNC, "WUPSConfigAPI_GetVersion", (void **) &sAPIGetVersion) != OS_DYNLOAD_OK) {
WUPS_DEBUG_REPORT("libuwps: FindExport WUPSConfigAPI_GetVersion failed.\n");
return WUPSCONFIG_API_RESULT_MODULE_MISSING_EXPORT;
}
auto res = WUPSConfigAPI_GetVersion(&sConfigAPIVersion);
if (res != WUPSCONFIG_API_RESULT_SUCCESS) {
return WUPSCONFIG_API_RESULT_UNSUPPORTED_VERSION;
}
if (OSDynLoad_FindExport(sModuleHandle, OS_DYNLOAD_EXPORT_FUNC, "WUPSConfigAPI_InitEx", (void **) &sAPIInitEx) != OS_DYNLOAD_OK) {
WUPS_DEBUG_REPORT("libwups: FindExport WUPSConfigAPI_InitEx failed.\n");
return WUPSCONFIG_API_RESULT_MODULE_MISSING_EXPORT;
}
if (OSDynLoad_FindExport(sModuleHandle, OS_DYNLOAD_EXPORT_FUNC, "WUPSConfigAPI_Category_CreateEx", (void **) &sAPICategoryCreateEx) != OS_DYNLOAD_OK) {
WUPS_DEBUG_REPORT("libwups: FindExport WUPSConfigAPI_Category_CreateEx failed.\n");
return WUPSCONFIG_API_RESULT_MODULE_MISSING_EXPORT;
}
if (OSDynLoad_FindExport(sModuleHandle, OS_DYNLOAD_EXPORT_FUNC, "WUPSConfigAPI_Category_Destroy", (void **) &sAPICategoryDestroy) != OS_DYNLOAD_OK) {
WUPS_DEBUG_REPORT("libwups: FindExport WUPSConfigAPI_Category_Destroy failed.\n");
return WUPSCONFIG_API_RESULT_MODULE_MISSING_EXPORT;
}
if (OSDynLoad_FindExport(sModuleHandle, OS_DYNLOAD_EXPORT_FUNC, "WUPSConfigAPI_Category_AddCategory", (void **) &sAPICategoryAddCategory) != OS_DYNLOAD_OK) {
WUPS_DEBUG_REPORT("libwups: FindExport WUPSConfigAPI_Category_AddCategory failed.\n");
return WUPSCONFIG_API_RESULT_MODULE_MISSING_EXPORT;
}
if (OSDynLoad_FindExport(sModuleHandle, OS_DYNLOAD_EXPORT_FUNC, "WUPSConfigAPI_Category_AddItem", (void **) &sAPICategoryAddItem) != OS_DYNLOAD_OK) {
WUPS_DEBUG_REPORT("libwups: FindExport WUPSConfigAPI_Category_AddItem failed.\n");
return WUPSCONFIG_API_RESULT_MODULE_MISSING_EXPORT;
}
if (OSDynLoad_FindExport(sModuleHandle, OS_DYNLOAD_EXPORT_FUNC, "WUPSConfigAPI_Item_CreateEx", (void **) &sAPIItemCreateEx) != OS_DYNLOAD_OK) {
WUPS_DEBUG_REPORT("libwups: FindExport WUPSConfigAPI_Item_Create failed.\n");
return WUPSCONFIG_API_RESULT_MODULE_MISSING_EXPORT;
}
if (OSDynLoad_FindExport(sModuleHandle, OS_DYNLOAD_EXPORT_FUNC, "WUPSConfigAPI_Item_Destroy", (void **) &sAPIItemDestroy) != OS_DYNLOAD_OK) {
WUPS_DEBUG_REPORT("libwups: FindExport WUPSConfigAPI_Item_Destroy failed.\n");
return WUPSCONFIG_API_RESULT_MODULE_MISSING_EXPORT;
}
sConfigLibInitDone = true;
sConfigPluginIdentifier = args.plugin_identifier;
return WUPSCONFIG_API_RESULT_SUCCESS;
}
extern "C" WUPSConfigAPIStatus WUPSConfigAPI_DeInitLibrary_Internal() {
return WUPSCONFIG_API_RESULT_SUCCESS;
}
WUPSConfigAPIStatus WUPSConfigAPI_GetVersion(WUPSConfigAPIVersion *outVariable) {
if (sAPIGetVersion == nullptr) {
if (OSDynLoad_Acquire("homebrew_notifications", &sModuleHandle) != OS_DYNLOAD_OK) {
WUPS_DEBUG_REPORT("libwups: OSDynLoad_Acquire failed.\n");
return WUPSCONFIG_API_RESULT_MODULE_NOT_FOUND;
}
if (OSDynLoad_FindExport(sModuleHandle, OS_DYNLOAD_EXPORT_FUNC, "WUPSConfigAPI_GetVersion", (void **) &sAPIGetVersion) != OS_DYNLOAD_OK) {
WUPS_DEBUG_REPORT("libwups: FindExport WUPSConfigAPI_GetVersion failed.\n");
return WUPSCONFIG_API_RESULT_MODULE_MISSING_EXPORT;
}
}
return sAPIGetVersion(outVariable);
}
WUPSConfigAPIStatus WUPSConfigAPI_InitEx(uint32_t pluginIdentifier, WUPSConfigAPIOptions options, WUPSConfigAPI_MenuOpenedCallback openedCallback, WUPSConfigAPI_MenuClosedCallback closedCallback) {
if (sConfigAPIVersion == WUPS_CONFIG_API_VERSION_ERROR) {
return WUPSCONFIG_API_RESULT_LIB_UNINITIALIZED;
}
if (sAPIInitEx == nullptr || sConfigAPIVersion < 1) {
return WUPSCONFIG_API_RESULT_UNSUPPORTED_COMMAND;
}
if (openedCallback == nullptr || closedCallback == nullptr || options.version != 1) {
return WUPSCONFIG_API_RESULT_INVALID_ARGUMENT;
}
return sAPIInitEx(pluginIdentifier, options, openedCallback, closedCallback);
}
WUPSConfigAPIStatus WUPSConfigAPI_Init(WUPSConfigAPIOptionsV1 optionsV1, WUPSConfigAPI_MenuOpenedCallback openedCallback, WUPSConfigAPI_MenuClosedCallback closedCallback) {
if (sConfigPluginIdentifier == 0xFFFFFFFF) {
return WUPSCONFIG_API_RESULT_INVALID_PLUGIN_IDENTIFIER;
}
WUPSConfigAPIOptions options = {
.version = WUPS_API_CONFIG_API_OPTION_VERSION_V1,
.data = {.v1 = optionsV1},
};
return WUPSConfigAPI_InitEx(sConfigPluginIdentifier, options, openedCallback, closedCallback);
}
WUPSConfigAPIStatus WUPSConfigAPI_Category_CreateEx(WUPSConfigAPICreateCategoryOptions options, WUPSConfigCategoryHandle *out) {
if (sConfigAPIVersion == WUPS_CONFIG_API_VERSION_ERROR) {
return WUPSCONFIG_API_RESULT_LIB_UNINITIALIZED;
}
if (sAPICategoryCreateEx == nullptr || sConfigAPIVersion < 1) {
return WUPSCONFIG_API_RESULT_UNSUPPORTED_COMMAND;
}
if (out == nullptr) {
return WUPSCONFIG_API_RESULT_INVALID_ARGUMENT;
}
return sAPICategoryCreateEx(options, out);
}
WUPSConfigAPIStatus WUPSConfigAPI_Category_Destroy(WUPSConfigCategoryHandle handle) {
if (sConfigAPIVersion == WUPS_CONFIG_API_VERSION_ERROR) {
return WUPSCONFIG_API_RESULT_LIB_UNINITIALIZED;
}
if (sAPICategoryDestroy == nullptr || sConfigAPIVersion < 1) {
return WUPSCONFIG_API_RESULT_UNSUPPORTED_COMMAND;
}
if (handle == nullptr) {
return WUPSCONFIG_API_RESULT_INVALID_ARGUMENT;
}
return sAPICategoryDestroy(handle);
}
WUPSConfigAPIStatus WUPSConfigAPI_Category_AddCategory(WUPSConfigCategoryHandle parentHandle, WUPSConfigCategoryHandle categoryHandle) {
if (sConfigAPIVersion == WUPS_CONFIG_API_VERSION_ERROR) {
return WUPSCONFIG_API_RESULT_LIB_UNINITIALIZED;
}
if (sAPICategoryAddCategory == nullptr || sConfigAPIVersion < 1) {
return WUPSCONFIG_API_RESULT_UNSUPPORTED_COMMAND;
}
if (parentHandle == nullptr || categoryHandle == nullptr) {
return WUPSCONFIG_API_RESULT_INVALID_ARGUMENT;
}
return sAPICategoryAddCategory(parentHandle, categoryHandle);
}
WUPSConfigAPIStatus WUPSConfigAPI_Category_AddItem(WUPSConfigCategoryHandle parentHandle, WUPSConfigItemHandle itemHandle) {
if (sConfigAPIVersion == WUPS_CONFIG_API_VERSION_ERROR) {
return WUPSCONFIG_API_RESULT_LIB_UNINITIALIZED;
}
if (sAPICategoryAddItem == nullptr || sConfigAPIVersion < 1) {
return WUPSCONFIG_API_RESULT_UNSUPPORTED_COMMAND;
}
if (parentHandle == 0 || itemHandle == 0) {
return WUPSCONFIG_API_RESULT_INVALID_ARGUMENT;
}
return sAPICategoryAddItem(parentHandle, itemHandle);
}
WUPSConfigAPIStatus WUPSConfigAPI_Item_CreateEx(WUPSConfigAPICreateItemOptions options, WUPSConfigItemHandle *out) {
if (sConfigAPIVersion == WUPS_CONFIG_API_VERSION_ERROR) {
return WUPSCONFIG_API_RESULT_LIB_UNINITIALIZED;
}
if (sAPIItemCreateEx == nullptr || sConfigAPIVersion < 1) {
return WUPSCONFIG_API_RESULT_UNSUPPORTED_COMMAND;
}
if (out == nullptr) {
return WUPSCONFIG_API_RESULT_INVALID_ARGUMENT;
}
return sAPIItemCreateEx(options, out);
}
WUPSConfigAPIStatus WUPSConfigAPI_Item_Destroy(WUPSConfigItemHandle handle) {
if (sConfigAPIVersion == WUPS_CONFIG_API_VERSION_ERROR) {
return WUPSCONFIG_API_RESULT_LIB_UNINITIALIZED;
}
if (sAPIItemDestroy == nullptr || sConfigAPIVersion < 1) {
return WUPSCONFIG_API_RESULT_UNSUPPORTED_COMMAND;
}
if (handle == nullptr) {
return WUPSCONFIG_API_RESULT_INVALID_ARGUMENT;
}
return sAPIItemDestroy(handle);
}

View File

@ -1,6 +1,8 @@
#include "wups_reent.h"
#include "wups_thread_specific.h"
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <stdint.h>
extern "C" void OSFatal(const char *);
@ -53,3 +55,60 @@ extern "C" void *__attribute__((weak)) wut_get_thread_specific(__wut_thread_spec
void *wut_get_thread_specific(__wut_thread_specific_id id) {
return wups_get_thread_specific(id);
}
extern "C" const char wups_meta_plugin_name[];
extern "C" void __attribute__((weak)) abort(void);
extern "C" void __attribute__((weak)) __assert_func(const char *file, int line, const char *func, const char *failedexpr);
extern "C" void __attribute__((weak)) __assert(const char *file, int line, const char *failedexpr);
void __attribute__((weak))
abort(void) {
char buffer[512] = {};
strcat(buffer, "Wii U Plugin System (plugin: \"");
strcat(buffer, wups_meta_plugin_name);
strcat(buffer, "\"):\n Abort called. Uncaught exception?");
OSFatal(buffer);
/* NOTREACHED */
while (1)
;
}
void __attribute__((weak))
__assert_func(const char *file,
int line,
const char *func,
const char *failedexpr) {
char tmp[512] = {};
char buffer[512] = {};
snprintf(tmp, sizeof(tmp), "Wii U Plugin System (plugin: \"%s\"):\n\n"
"assertion \"%s\" failed:\n\n"
"file \"%s\", line %d%s%s",
wups_meta_plugin_name, failedexpr, file, line, func ? ", function: " : "", func ? func : "");
// make sure to add a \n every 64 characters to fit on the DRC screen.
char *target_ptr = buffer;
int i = 0, j = 0, lineLength = 0;
while (tmp[i] != '\0' && j < (int) sizeof(buffer) - 2) {
if (tmp[i] == '\n') {
lineLength = 0;
} else if (lineLength >= 64) {
target_ptr[j++] = '\n';
lineLength = 0;
}
target_ptr[j++] = tmp[i++];
lineLength++;
}
OSFatal(buffer);
/* NOTREACHED */
}
void __attribute__((weak))
__assert(const char *file,
int line,
const char *failedexpr) {
__assert_func(file, line, NULL, failedexpr);
/* NOTREACHED */
}

View File

@ -1,633 +1,158 @@
#include <cstdlib>
#include <cstring>
#include <wups.h>
#include <wups/storage.h>
#include "utils/base64.h"
struct wups_internal_functions_t {
WUPSStorage_SaveFunction save_function_ptr = nullptr;
WUPSStorage_ForceReloadFunction force_reload_function_ptr = nullptr;
WUPSStorage_WipeStorageFunction wipe_storage_function_ptr = nullptr;
WUPSStorage_DeleteItemFunction delete_item_function_ptr = nullptr;
WUPSStorage_CreateSubItemFunction create_sub_item_function_ptr = nullptr;
WUPSStorage_GetSubItemFunction get_sub_item_function_ptr = nullptr;
WUPSStorage_StoreItemFunction store_item_function_ptr = nullptr;
WUPSStorage_GetItemFunction get_item_function_ptr = nullptr;
WUPSStorage_GetItemSizeFunction get_item_size_function_ptr = nullptr;
wups_storage_root_item __storageroot_item = nullptr;
};
static OpenStorageFunction openfunction_ptr __attribute__((section(".data"))) = nullptr;
static CloseStorageFunction closefunction_ptr __attribute__((section(".data"))) = nullptr;
static char plugin_id[256] __attribute__((section(".data")));
static wups_internal_functions_t __internal_functions __attribute__((section(".data"))) = {};
static uint32_t storage_initialized __attribute__((section(".data"))) = false;
static uint32_t isOpened __attribute__((section(".data")));
static uint32_t isDirty __attribute__((section(".data")));
static wups_storage_item_t rootItem __attribute__((section(".data")));
WUPSStorageError WUPSStorageAPI_InitInternal(wups_loader_init_storage_args_t args) {
if (args.version > WUPS_STORAGE_CUR_API_VERSION) {
__internal_functions = {};
return WUPS_STORAGE_ERROR_INTERNAL_INVALID_VERSION;
}
__internal_functions.__storageroot_item = args.root_item;
__internal_functions.save_function_ptr = args.save_function_ptr;
__internal_functions.force_reload_function_ptr = args.force_reload_function_ptr;
__internal_functions.wipe_storage_function_ptr = args.wipe_storage_function_ptr;
__internal_functions.delete_item_function_ptr = args.delete_item_function_ptr;
__internal_functions.create_sub_item_function_ptr = args.create_sub_item_function_ptr;
__internal_functions.get_sub_item_function_ptr = args.get_sub_item_function_ptr;
__internal_functions.store_item_function_ptr = args.store_item_function_ptr;
__internal_functions.get_item_function_ptr = args.get_item_function_ptr;
__internal_functions.get_item_size_function_ptr = args.get_item_size_function_ptr;
static wups_storage_item_t *sActiveSubItem __attribute__((section(".data"))) = nullptr;
void WUPS_InitStorage(wups_loader_init_storage_args_t args) {
openfunction_ptr = args.open_storage_ptr;
closefunction_ptr = args.close_storage_ptr;
strncpy(plugin_id, args.plugin_id, sizeof(plugin_id) - 1);
storage_initialized = true;
isOpened = false;
isDirty = false;
sActiveSubItem = nullptr;
rootItem.key = nullptr;
rootItem.data = nullptr;
rootItem.data_size = 0;
rootItem.deleted = false;
rootItem.type = WUPS_STORAGE_TYPE_ITEM;
return WUPS_STORAGE_ERROR_SUCCESS;
}
const char *WUPS_GetStorageStatusStr(WUPSStorageError status) {
const char *WUPSStorageAPI_GetStatusStr(WUPSStorageError status) {
switch (status) {
case WUPS_STORAGE_ERROR_SUCCESS:
return "WUPS_STORAGE_ERROR_SUCCESS";
case WUPS_STORAGE_ERROR_NOT_OPENED:
return "WUPS_STORAGE_ERROR_NOT_OPENED";
case WUPS_STORAGE_ERROR_ALREADY_OPENED:
return "WUPS_STORAGE_ERROR_ALREADY_OPENED";
case WUPS_STORAGE_ERROR_INVALID_ARGS:
return "WUPS_STORAGE_ERROR_INVALID_ARGS";
case WUPS_STORAGE_ERROR_NOT_FOUND:
return "WUPS_STORAGE_ERROR_NOT_FOUND";
case WUPS_STORAGE_ERROR_NOT_INITIALIZED:
return "WUPS_STORAGE_ERROR_NOT_INITIALIZED";
case WUPS_STORAGE_ERROR_INVALID_BACKEND_PARAMS:
return "WUPS_STORAGE_ERROR_INVALID_BACKEND_PARAMS";
case WUPS_STORAGE_ERROR_INVALID_JSON:
return "WUPS_STORAGE_ERROR_INVALID_JSON";
case WUPS_STORAGE_ERROR_IO:
return "WUPS_STORAGE_ERROR_IO";
case WUPS_STORAGE_ERROR_B64_DECODE_FAILED:
return "WUPS_STORAGE_ERROR_B64_DECODE_FAILED";
case WUPS_STORAGE_ERROR_BUFFER_TOO_SMALL:
return "WUPS_STORAGE_ERROR_BUFFER_TOO_SMALL";
case WUPS_STORAGE_ERROR_MALLOC_FAILED:
return "WUPS_STORAGE_ERROR_MALLOC_FAILED";
case WUPS_STORAGE_ERROR_NOT_ACTIVE_CATEGORY:
return "WUPS_STORAGE_ERROR_NOT_ACTIVE_CATEGORY";
case WUPS_STORAGE_ERROR_UNEXPECTED_DATA_TYPE:
return "WUPS_STORAGE_ERROR_UNEXPECTED_DATA_TYPE";
case WUPS_STORAGE_ERROR_BUFFER_TOO_SMALL:
return "WUPS_STORAGE_ERROR_BUFFER_TOO_SMALL";
case WUPS_STORAGE_ERROR_ALREADY_EXISTS:
return "WUPS_STORAGE_ERROR_ALREADY_EXISTS";
case WUPS_STORAGE_ERROR_IO_ERROR:
return "WUPS_STORAGE_ERROR_IO_ERROR";
case WUPS_STORAGE_ERROR_NOT_FOUND:
return "WUPS_STORAGE_ERROR_NOT_FOUND";
case WUPS_STORAGE_ERROR_INTERNAL_NOT_INITIALIZED:
return "WUPS_STORAGE_ERROR_INTERNAL_NOT_INITIALIZED";
case WUPS_STORAGE_ERROR_INTERNAL_INVALID_VERSION:
return "WUPS_STORAGE_ERROR_INTERNAL_INVALID_VERSION";
case WUPS_STORAGE_ERROR_UNKNOWN_ERROR:
return "WUPS_STORAGE_ERROR_UNKNOWN_ERROR";
}
return "WUPS_STORAGE_ERROR_UNKNOWN";
}
WUPSStorageError WUPS_OpenStorage(void) {
if (!storage_initialized) {
return WUPS_STORAGE_ERROR_NOT_INITIALIZED;
WUPSStorageError WUPSStorageAPI_SaveStorage(bool force) {
if (__internal_functions.save_function_ptr == nullptr) {
return WUPS_STORAGE_ERROR_INTERNAL_NOT_INITIALIZED;
}
if (isOpened) {
return WUPS_STORAGE_ERROR_ALREADY_OPENED;
if (__internal_functions.__storageroot_item == nullptr) {
return WUPS_STORAGE_ERROR_INTERNAL_NOT_INITIALIZED;
}
WUPSStorageError result = openfunction_ptr(plugin_id, &rootItem);
if (result == WUPS_STORAGE_ERROR_SUCCESS || result == WUPS_STORAGE_ERROR_INVALID_JSON) {
isOpened = true;
isDirty = false;
}
return result;
return __internal_functions.save_function_ptr(__internal_functions.__storageroot_item, force);
}
static void closeItem(wups_storage_item_t *item) {
if (!item) {
return;
WUPSStorageError WUPSStorageAPI_ForceReloadStorage() {
if (__internal_functions.force_reload_function_ptr == nullptr) {
return WUPS_STORAGE_ERROR_INTERNAL_NOT_INITIALIZED;
}
if (item->type == WUPS_STORAGE_TYPE_ITEM) {
auto *items = (wups_storage_item_t *) item->data;
for (uint32_t i = 0; i < item->data_size; i++) {
closeItem(&items[i]);
}
if (__internal_functions.__storageroot_item == nullptr) {
return WUPS_STORAGE_ERROR_INTERNAL_NOT_INITIALIZED;
}
free(item->data);
free(item->key);
return __internal_functions.force_reload_function_ptr(__internal_functions.__storageroot_item);
}
WUPSStorageError WUPS_CloseStorage(void) {
if (!storage_initialized) {
return WUPS_STORAGE_ERROR_NOT_INITIALIZED;
WUPSStorageError WUPSStorageAPI_WipeStorage() {
if (__internal_functions.wipe_storage_function_ptr == nullptr) {
return WUPS_STORAGE_ERROR_INTERNAL_NOT_INITIALIZED;
}
if (!isOpened) {
return WUPS_STORAGE_ERROR_NOT_OPENED;
if (__internal_functions.__storageroot_item == nullptr) {
return WUPS_STORAGE_ERROR_INTERNAL_NOT_INITIALIZED;
}
WUPSStorageError result = WUPS_STORAGE_ERROR_SUCCESS;
if (isDirty) {
result = closefunction_ptr(plugin_id, &rootItem);
}
if (result == WUPS_STORAGE_ERROR_SUCCESS) {
isOpened = false;
isDirty = false;
closeItem(&rootItem);
rootItem.data_size = 0;
rootItem.data = nullptr;
rootItem.key = nullptr;
}
sActiveSubItem = nullptr;
return result;
return __internal_functions.wipe_storage_function_ptr(__internal_functions.__storageroot_item);
}
WUPSStorageError WUPS_DeleteItem(wups_storage_item_t *parent, const char *key) {
if (!storage_initialized) {
return WUPS_STORAGE_ERROR_NOT_INITIALIZED;
WUPSStorageError WUPSStorageAPI_DeleteItem(wups_storage_item parent, const char *key) {
if (__internal_functions.delete_item_function_ptr == nullptr) {
return WUPS_STORAGE_ERROR_INTERNAL_NOT_INITIALIZED;
}
if (!isOpened) {
return WUPS_STORAGE_ERROR_NOT_OPENED;
if (__internal_functions.__storageroot_item == nullptr) {
return WUPS_STORAGE_ERROR_INTERNAL_NOT_INITIALIZED;
}
return __internal_functions.delete_item_function_ptr(__internal_functions.__storageroot_item, parent, key);
}
if (!key) {
WUPSStorageError WUPSStorageAPI_CreateSubItem(wups_storage_item parent, const char *key, wups_storage_item *outItem) {
if (__internal_functions.create_sub_item_function_ptr == nullptr) {
return WUPS_STORAGE_ERROR_INTERNAL_NOT_INITIALIZED;
}
if (__internal_functions.__storageroot_item == nullptr) {
return WUPS_STORAGE_ERROR_INTERNAL_NOT_INITIALIZED;
}
return __internal_functions.create_sub_item_function_ptr(__internal_functions.__storageroot_item, parent, key, outItem);
}
WUPSStorageError WUPSStorageAPI_GetSubItem(wups_storage_item parent, const char *key, wups_storage_item *outItem) {
if (__internal_functions.get_sub_item_function_ptr == nullptr) {
return WUPS_STORAGE_ERROR_INTERNAL_NOT_INITIALIZED;
}
if (__internal_functions.__storageroot_item == nullptr) {
return WUPS_STORAGE_ERROR_INTERNAL_NOT_INITIALIZED;
}
return __internal_functions.get_sub_item_function_ptr(__internal_functions.__storageroot_item, parent, key, outItem);
}
WUPSStorageError WUPSStorageAPI_StoreItem(wups_storage_item parent, const char *key, WUPSStorageItemType type, void *data, uint32_t size) {
if (__internal_functions.store_item_function_ptr == nullptr) {
return WUPS_STORAGE_ERROR_INTERNAL_NOT_INITIALIZED;
}
if (__internal_functions.__storageroot_item == nullptr) {
return WUPS_STORAGE_ERROR_INTERNAL_NOT_INITIALIZED;
}
return __internal_functions.store_item_function_ptr(__internal_functions.__storageroot_item, parent, key, type, data, size);
}
WUPSStorageError WUPSStorageAPI_GetItem(wups_storage_item parent, const char *key, WUPSStorageItemType type, void *data, uint32_t maxSize, uint32_t *outSize) {
if (data == nullptr) {
return WUPS_STORAGE_ERROR_INVALID_ARGS;
}
if (!parent) {
parent = &rootItem;
} else {
// We can only safely process items of a parent if the parent was the last
// item returned by WUPS_GetSubItem or WUPS_CreateSubItem
if (parent != sActiveSubItem) {
return WUPS_STORAGE_ERROR_NOT_ACTIVE_CATEGORY;
}
if (parent->type != WUPS_STORAGE_TYPE_ITEM) {
return WUPS_STORAGE_ERROR_INVALID_ARGS;
}
if (__internal_functions.get_item_function_ptr == nullptr) {
return WUPS_STORAGE_ERROR_INTERNAL_NOT_INITIALIZED;
}
isDirty = true;
for (uint32_t i = 0; i < parent->data_size; i++) {
wups_storage_item_t *item = &((wups_storage_item_t *) parent->data)[i];
if (item->deleted || item->type == WUPS_STORAGE_TYPE_INVALID) {
continue;
}
if (strcmp(item->key, key) == 0) {
free(item->data);
free(item->key);
item->key = nullptr;
item->data = nullptr;
item->deleted = true;
if (sActiveSubItem == item) {
sActiveSubItem = nullptr;
}
return WUPS_STORAGE_ERROR_SUCCESS;
}
if (__internal_functions.__storageroot_item == nullptr) {
return WUPS_STORAGE_ERROR_INTERNAL_NOT_INITIALIZED;
}
return WUPS_STORAGE_ERROR_NOT_FOUND;
return __internal_functions.get_item_function_ptr(__internal_functions.__storageroot_item, parent, key, type, data, maxSize, outSize);
}
// int32_t WUPS_GetSize(const char* key) {
// if (!storage_initialized) {
// return WUPS_STORAGE_ERROR_NOT_INITIALIZED;
// }
// if (!isOpened) {
// return WUPS_STORAGE_ERROR_NOT_OPENED;
// }
// for (uint32_t i = 0; i < amount_of_items; i++) {
// wups_loader_storage_item_t* item = &items[i];
// if (item->pending_delete || item->type == WUPS_STORAGE_TYPE_INVALID) {
// continue;
// }
// if (strcmp(item->key, key) == 0) {
// return item->data_size;
// }
// }
// return WUPS_STORAGE_ERROR_NOT_FOUND;
// }
static wups_storage_item_t *addItem(wups_storage_item_t *parent, const char *key, wups_storage_type_t type, WUPSStorageError *error) {
wups_storage_item_t *foundItem = nullptr;
// First check for existing item with the same name.
for (uint32_t i = 0; i < parent->data_size; i++) {
wups_storage_item_t *item = &((wups_storage_item_t *) parent->data)[i];
if (item->key && strcmp(item->key, key) == 0) {
free(item->data);
foundItem = item;
break;
}
}
if (!foundItem) {
// Then check if there are any deleted item we can override.
for (uint32_t i = 0; i < parent->data_size; i++) {
wups_storage_item_t *item = &((wups_storage_item_t *) parent->data)[i];
if (item->deleted) {
free(item->data);
free(item->key);
item->data = nullptr;
item->key = nullptr;
item->key = strdup(key);
if (item->key == nullptr) {
return nullptr;
}
foundItem = item;
break;
}
}
}
if (!foundItem) {
auto *newPtr = (wups_storage_item_t *) realloc(parent->data, (parent->data_size + 1) * sizeof(wups_storage_item_t));
if (newPtr == nullptr) {
*error = WUPS_STORAGE_ERROR_MALLOC_FAILED;
return nullptr;
}
parent->data = newPtr;
foundItem = &((wups_storage_item_t *) parent->data)[parent->data_size];
memset(foundItem, 0, sizeof(wups_storage_item_t));
foundItem->deleted = true;
parent->data_size += 1;
foundItem->key = strdup(key);
if (foundItem->key == nullptr) {
*error = WUPS_STORAGE_ERROR_MALLOC_FAILED;
return nullptr;
}
}
foundItem->type = type;
foundItem->deleted = false;
foundItem->data = nullptr;
foundItem->data_size = 0;
return foundItem;
}
WUPSStorageError WUPS_CreateSubItem(wups_storage_item_t *parent, const char *key, wups_storage_item_t **outItem) {
if (!storage_initialized) {
return WUPS_STORAGE_ERROR_NOT_INITIALIZED;
}
if (!isOpened) {
return WUPS_STORAGE_ERROR_NOT_OPENED;
}
if (!key || !outItem) {
WUPSStorageError WUPSStorageAPI_GetItemSize(wups_storage_item parent, const char *key, WUPSStorageItemType itemType, uint32_t *outSize) {
if (outSize == nullptr) {
return WUPS_STORAGE_ERROR_INVALID_ARGS;
}
if (!parent) {
parent = &rootItem;
} else {
// We can only safely process items of a parent if the parent was the last
// item returned by WUPS_GetSubItem or WUPS_CreateSubItem
if (parent != sActiveSubItem) {
return WUPS_STORAGE_ERROR_NOT_ACTIVE_CATEGORY;
}
if (parent->type != WUPS_STORAGE_TYPE_ITEM) {
return WUPS_STORAGE_ERROR_INVALID_ARGS;
}
if (__internal_functions.get_item_size_function_ptr == nullptr) {
return WUPS_STORAGE_ERROR_INTERNAL_NOT_INITIALIZED;
}
isDirty = true;
WUPSStorageError error;
wups_storage_item_t *item = addItem(parent, key, WUPS_STORAGE_TYPE_ITEM, &error);
if (item == nullptr) {
return error;
}
sActiveSubItem = item;
*outItem = item;
return WUPS_STORAGE_ERROR_SUCCESS;
}
WUPSStorageError WUPS_GetSubItem(wups_storage_item_t *parent, const char *key, wups_storage_item_t **outItem) {
if (!storage_initialized) {
return WUPS_STORAGE_ERROR_NOT_INITIALIZED;
}
if (!isOpened) {
return WUPS_STORAGE_ERROR_NOT_OPENED;
}
if (!key || outItem == nullptr) {
return WUPS_STORAGE_ERROR_INVALID_ARGS;
}
if (!parent) {
parent = &rootItem;
} else {
// We can only safely process items of a parent if the parent was the last
// item returned by WUPS_GetSubItem or WUPS_CreateSubItem
if (parent != sActiveSubItem) {
return WUPS_STORAGE_ERROR_NOT_ACTIVE_CATEGORY;
}
if (parent->type != WUPS_STORAGE_TYPE_ITEM) {
return WUPS_STORAGE_ERROR_INVALID_ARGS;
}
}
for (uint32_t i = 0; i < parent->data_size; i++) {
wups_storage_item_t *item = &((wups_storage_item_t *) parent->data)[i];
if (item->deleted || item->type != WUPS_STORAGE_TYPE_ITEM) {
continue;
}
if (strcmp(item->key, key) == 0) {
sActiveSubItem = item;
*outItem = item;
return WUPS_STORAGE_ERROR_SUCCESS;
}
}
return WUPS_STORAGE_ERROR_NOT_FOUND;
}
WUPSStorageError WUPS_StoreString(wups_storage_item_t *parent, const char *key, const char *string) {
if (!storage_initialized) {
return WUPS_STORAGE_ERROR_NOT_INITIALIZED;
}
if (!isOpened) {
return WUPS_STORAGE_ERROR_NOT_OPENED;
}
if (!key || !string) {
return WUPS_STORAGE_ERROR_INVALID_ARGS;
}
if (!parent) {
parent = &rootItem;
} else {
// We can only safely process items of a parent if the parent was the last
// item returned by WUPS_GetSubItem or WUPS_CreateSubItem
if (parent != sActiveSubItem) {
return WUPS_STORAGE_ERROR_NOT_ACTIVE_CATEGORY;
}
if (parent->type != WUPS_STORAGE_TYPE_ITEM) {
return WUPS_STORAGE_ERROR_INVALID_ARGS;
}
}
isDirty = true;
WUPSStorageError error;
wups_storage_item_t *item = addItem(parent, key, WUPS_STORAGE_TYPE_STRING, &error);
if (item == nullptr) {
return error;
}
uint32_t size = strlen(string) + 1;
item->data = malloc(size);
if (item->data == nullptr) {
item->key = nullptr;
item->deleted = true;
return WUPS_STORAGE_ERROR_MALLOC_FAILED;
}
item->data_size = size;
strcpy((char *) item->data, string);
return WUPS_STORAGE_ERROR_SUCCESS;
}
WUPSStorageError WUPS_StoreBool(wups_storage_item_t *parent, const char *key, bool value) {
return WUPS_StoreInt(parent, key, (int32_t) value);
}
WUPSStorageError WUPS_StoreInt(wups_storage_item_t *parent, const char *key, int32_t value) {
if (!storage_initialized) {
return WUPS_STORAGE_ERROR_NOT_INITIALIZED;
}
if (!isOpened) {
return WUPS_STORAGE_ERROR_NOT_OPENED;
}
if (!key) {
return WUPS_STORAGE_ERROR_INVALID_ARGS;
}
if (!parent) {
parent = &rootItem;
} else {
// We can only safely process items of a parent if the parent was the last
// item returned by WUPS_GetSubItem or WUPS_CreateSubItem
if (parent != sActiveSubItem) {
return WUPS_STORAGE_ERROR_NOT_ACTIVE_CATEGORY;
}
if (parent->type != WUPS_STORAGE_TYPE_ITEM) {
return WUPS_STORAGE_ERROR_INVALID_ARGS;
}
}
isDirty = true;
WUPSStorageError error;
wups_storage_item_t *item = addItem(parent, key, WUPS_STORAGE_TYPE_INT, &error);
if (item == nullptr) {
return error;
}
item->data = malloc(sizeof(int32_t));
if (item->data == nullptr) {
item->key = nullptr;
item->deleted = true;
return WUPS_STORAGE_ERROR_MALLOC_FAILED;
}
item->data_size = sizeof(int32_t);
*(int32_t *) item->data = value;
return WUPS_STORAGE_ERROR_SUCCESS;
}
WUPSStorageError WUPS_StoreBinary(wups_storage_item_t *parent, const char *key, const void *data, uint32_t size) {
if (!storage_initialized) {
return WUPS_STORAGE_ERROR_NOT_INITIALIZED;
}
if (!isOpened) {
return WUPS_STORAGE_ERROR_NOT_OPENED;
}
if (!key || !data || size == 0) {
return WUPS_STORAGE_ERROR_INVALID_ARGS;
}
if (!parent) {
parent = &rootItem;
} else {
// We can only safely process items of a parent if the parent was the last
// item returned by WUPS_GetSubItem or WUPS_CreateSubItem
if (parent != sActiveSubItem) {
return WUPS_STORAGE_ERROR_NOT_ACTIVE_CATEGORY;
}
if (parent->type != WUPS_STORAGE_TYPE_ITEM) {
return WUPS_STORAGE_ERROR_INVALID_ARGS;
}
}
isDirty = true;
WUPSStorageError error;
wups_storage_item_t *item = addItem(parent, key, WUPS_STORAGE_TYPE_STRING, &error);
if (item == nullptr) {
return error;
}
item->data = b64_encode((const uint8_t *) data, size);
if (item->data == nullptr) {
item->key = nullptr;
item->deleted = true;
return WUPS_STORAGE_ERROR_MALLOC_FAILED;
}
item->data_size = strlen((char *) data) + 1;
return WUPS_STORAGE_ERROR_SUCCESS;
}
WUPSStorageError WUPS_GetString(wups_storage_item_t *parent, const char *key, char *outString, uint32_t maxSize) {
if (!storage_initialized) {
return WUPS_STORAGE_ERROR_NOT_INITIALIZED;
}
if (!isOpened) {
return WUPS_STORAGE_ERROR_NOT_OPENED;
}
if (!key || !outString || maxSize == 0) {
return WUPS_STORAGE_ERROR_INVALID_ARGS;
}
if (!parent) {
parent = &rootItem;
} else {
// We can only safely process items of a parent if the parent was the last
// item returned by WUPS_GetSubItem or WUPS_CreateSubItem
if (parent != sActiveSubItem) {
return WUPS_STORAGE_ERROR_NOT_ACTIVE_CATEGORY;
}
if (parent->type != WUPS_STORAGE_TYPE_ITEM) {
return WUPS_STORAGE_ERROR_INVALID_ARGS;
}
}
for (uint32_t i = 0; i < parent->data_size; i++) {
wups_storage_item_t *item = &((wups_storage_item_t *) parent->data)[i];
if (item->deleted || item->type != WUPS_STORAGE_TYPE_STRING) {
continue;
}
if (strcmp(item->key, key) == 0) {
strncpy(outString, (char *) item->data, maxSize);
return WUPS_STORAGE_ERROR_SUCCESS;
}
}
return WUPS_STORAGE_ERROR_NOT_FOUND;
}
WUPSStorageError WUPS_GetBool(wups_storage_item_t *parent, const char *key, bool *outBool) {
int32_t out;
WUPSStorageError result = WUPS_GetInt(parent, key, &out);
if (result != WUPS_STORAGE_ERROR_SUCCESS) {
return result;
}
*outBool = out != 0;
return WUPS_STORAGE_ERROR_SUCCESS;
}
WUPSStorageError WUPS_GetInt(wups_storage_item_t *parent, const char *key, int32_t *outInt) {
if (!storage_initialized) {
return WUPS_STORAGE_ERROR_NOT_INITIALIZED;
}
if (!isOpened) {
return WUPS_STORAGE_ERROR_NOT_OPENED;
}
if (!key || !outInt) {
return WUPS_STORAGE_ERROR_INVALID_ARGS;
}
if (!parent) {
parent = &rootItem;
} else {
// We can only safely process items of a parent if the parent was the last
// item returned by WUPS_GetSubItem or WUPS_CreateSubItem
if (parent != sActiveSubItem) {
return WUPS_STORAGE_ERROR_NOT_ACTIVE_CATEGORY;
}
if (parent->type != WUPS_STORAGE_TYPE_ITEM) {
return WUPS_STORAGE_ERROR_INVALID_ARGS;
}
}
for (uint32_t i = 0; i < parent->data_size; i++) {
wups_storage_item_t *item = &((wups_storage_item_t *) parent->data)[i];
if (item->deleted || item->type != WUPS_STORAGE_TYPE_INT) {
continue;
}
if (strcmp(item->key, key) == 0) {
*outInt = *(int32_t *) item->data;
return WUPS_STORAGE_ERROR_SUCCESS;
}
}
return WUPS_STORAGE_ERROR_NOT_FOUND;
}
WUPSStorageError WUPS_GetBinary(wups_storage_item_t *parent, const char *key, void *outData, uint32_t maxSize) {
if (!storage_initialized) {
return WUPS_STORAGE_ERROR_NOT_INITIALIZED;
}
if (!isOpened) {
return WUPS_STORAGE_ERROR_NOT_OPENED;
}
if (!key || !outData || maxSize == 0) {
return WUPS_STORAGE_ERROR_INVALID_ARGS;
}
if (!parent) {
parent = &rootItem;
} else {
// We can only safely process items of a parent if the parent was the last
// item returned by WUPS_GetSubItem or WUPS_CreateSubItem
if (parent != sActiveSubItem) {
return WUPS_STORAGE_ERROR_NOT_ACTIVE_CATEGORY;
}
if (parent->type != WUPS_STORAGE_TYPE_ITEM) {
return WUPS_STORAGE_ERROR_INVALID_ARGS;
}
}
for (uint32_t i = 0; i < parent->data_size; i++) {
wups_storage_item_t *item = &((wups_storage_item_t *) parent->data)[i];
if (item->deleted || item->type != WUPS_STORAGE_TYPE_STRING) {
continue;
}
if (strcmp(item->key, key) == 0) {
if (b64_decoded_size((char *) item->data) > maxSize) {
return WUPS_STORAGE_ERROR_BUFFER_TOO_SMALL;
}
if (b64_decode((char *) item->data, (uint8_t *) outData, item->data_size)) {
return WUPS_STORAGE_ERROR_SUCCESS;
} else {
return WUPS_STORAGE_ERROR_B64_DECODE_FAILED;
}
}
}
return WUPS_STORAGE_ERROR_NOT_FOUND;
return __internal_functions.get_item_size_function_ptr(__internal_functions.__storageroot_item, parent, key, itemType, outSize);
}

View File

@ -0,0 +1,126 @@
#include <optional>
#include <stdexcept>
#include <string>
#include <wups/storage.h>
WUPSStorageError WUPSStorageAPI::DeleteItem(std::string_view key) noexcept {
WUPSStorageSubItem item(nullptr);
return item.DeleteItem(key);
}
WUPSStorageError WUPSStorageAPI::SaveStorage(bool forceSave) {
return WUPSStorageAPI_SaveStorage(forceSave);
}
WUPSStorageError WUPSStorageAPI::ForceReloadStorage() {
return WUPSStorageAPI_ForceReloadStorage();
}
WUPSStorageError WUPSStorageAPI::WipeStorage() {
return WUPSStorageAPI_WipeStorage();
}
std::optional<WUPSStorageSubItem> WUPSStorageAPI::CreateSubItem(std::string_view key, WUPSStorageError &err) noexcept {
WUPSStorageSubItem item(nullptr);
return item.CreateSubItem(key, err);
}
std::optional<WUPSStorageSubItem> WUPSStorageAPI::GetSubItem(std::string_view key, WUPSStorageError &err) noexcept {
WUPSStorageSubItem item(nullptr);
return item.GetSubItem(key, err);
}
std::optional<WUPSStorageSubItem> WUPSStorageAPI::GetOrCreateSubItem(std::string_view key, WUPSStorageError &err) noexcept {
WUPSStorageSubItem item(nullptr);
return item.GetOrCreateSubItem(key, err);
}
WUPSStorageSubItem WUPSStorageAPI::CreateSubItem(std::string_view key) {
WUPSStorageSubItem item(nullptr);
return item.CreateSubItem(key);
}
WUPSStorageSubItem WUPSStorageAPI::GetSubItem(std::string_view key) {
WUPSStorageSubItem item(nullptr);
return item.GetSubItem(key);
}
WUPSStorageSubItem WUPSStorageAPI::GetOrCreateSubItem(std::string_view key) {
WUPSStorageSubItem item(nullptr);
return item.GetOrCreateSubItem(key);
}
std::string_view WUPSStorageAPI::GetStatusStr(const WUPSStorageError &err) noexcept {
return WUPSStorageAPI_GetStatusStr(err);
}
WUPSStorageSubItem WUPSStorageAPI::GetRootItem() noexcept {
return WUPSStorageSubItem(nullptr);
}
WUPSStorageError WUPSStorageSubItem::DeleteItem(std::string_view key) noexcept {
return WUPSStorageAPI_DeleteItem(mHandle, key.data());
}
std::optional<WUPSStorageSubItem> WUPSStorageSubItem::CreateSubItem(std::string_view key, WUPSStorageError &err) noexcept {
wups_storage_item outItem = {};
err = WUPSStorageAPI_CreateSubItem(mHandle, key.data(), &outItem);
if (err != WUPS_STORAGE_ERROR_SUCCESS) {
return std::nullopt;
}
return WUPSStorageSubItem(outItem);
}
std::optional<WUPSStorageSubItem> WUPSStorageSubItem::GetSubItem(std::string_view key, WUPSStorageError &err) const noexcept {
wups_storage_item outItem = {};
err = WUPSStorageAPI_GetSubItem(mHandle, key.data(), &outItem);
if (err != WUPS_STORAGE_ERROR_SUCCESS) {
return std::nullopt;
}
return WUPSStorageSubItem(outItem);
}
std::optional<WUPSStorageSubItem> WUPSStorageSubItem::GetOrCreateSubItem(std::string_view key, WUPSStorageError &err) noexcept {
wups_storage_item outItem = {};
err = WUPSStorageAPI_GetSubItem(mHandle, key.data(), &outItem);
if (err == WUPS_STORAGE_ERROR_NOT_FOUND) {
return CreateSubItem(key, err);
}
return WUPSStorageSubItem(outItem);
}
WUPSStorageSubItem WUPSStorageSubItem::CreateSubItem(std::string_view key) {
WUPSStorageError err;
auto res = CreateSubItem(key, err);
if (!res) {
throw std::runtime_error(std::string("WUPSStorageSubItem::CreateSubItem(\"").append(key).append("\")").append(WUPSStorageAPI_GetStatusStr(err)));
}
return *res;
}
WUPSStorageSubItem WUPSStorageSubItem::GetSubItem(std::string_view key) const {
WUPSStorageError err;
auto res = GetSubItem(key, err);
if (!res) {
throw std::runtime_error(std::string("WUPSStorageSubItem::GetSubItem(\"").append(key).append("\")").append(WUPSStorageAPI_GetStatusStr(err)));
}
return *res;
}
WUPSStorageSubItem WUPSStorageSubItem::GetOrCreateSubItem(std::string_view key) {
WUPSStorageError err;
auto res = GetOrCreateSubItem(key, err);
if (!res) {
throw std::runtime_error(std::string("WUPSStorageSubItem::GetOrCreateSubItem(\"").append(key).append("\")").append(WUPSStorageAPI_GetStatusStr(err)));
}
return *res;
}
bool WUPSStorageSubItem::operator==(const WUPSStorageSubItem &rhs) const {
return mHandle == rhs.mHandle;
}
bool WUPSStorageSubItem::operator!=(const WUPSStorageSubItem &rhs) const {
return !(rhs == *this);
}

View File

@ -1,127 +0,0 @@
#include "base64.h"
#include <string.h>
static const char b64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
size_t b64_encoded_size(size_t inlen) {
size_t ret;
ret = inlen;
if (inlen % 3 != 0)
ret += 3 - (inlen % 3);
ret /= 3;
ret *= 4;
return ret;
}
char *b64_encode(const uint8_t *in, size_t len) {
char *out;
size_t elen;
size_t i;
size_t j;
size_t v;
if (in == NULL || len == 0)
return NULL;
elen = b64_encoded_size(len);
out = (char *) malloc(elen + 1);
out[elen] = '\0';
for (i = 0, j = 0; i < len; i += 3, j += 4) {
v = in[i];
v = i + 1 < len ? v << 8 | in[i + 1] : v << 8;
v = i + 2 < len ? v << 8 | in[i + 2] : v << 8;
out[j] = b64chars[(v >> 18) & 0x3F];
out[j + 1] = b64chars[(v >> 12) & 0x3F];
if (i + 1 < len) {
out[j + 2] = b64chars[(v >> 6) & 0x3F];
} else {
out[j + 2] = '=';
}
if (i + 2 < len) {
out[j + 3] = b64chars[v & 0x3F];
} else {
out[j + 3] = '=';
}
}
return out;
}
size_t b64_decoded_size(const char *in) {
size_t len;
size_t ret;
size_t i;
if (in == NULL)
return 0;
len = strlen(in);
ret = len / 4 * 3;
for (i = len; i-- > 0;) {
if (in[i] == '=') {
ret--;
} else {
break;
}
}
return ret;
}
static const int b64invs[] = {
62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3,
4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51};
static int b64_isvalidchar(char c) {
if (c >= '0' && c <= '9')
return 1;
if (c >= 'A' && c <= 'Z')
return 1;
if (c >= 'a' && c <= 'z')
return 1;
if (c == '+' || c == '/' || c == '=')
return 1;
return 0;
}
int b64_decode(const char *in, uint8_t *out, size_t outlen) {
size_t len;
size_t i;
size_t j;
int v;
if (in == NULL || out == NULL)
return 0;
len = strlen(in);
if (outlen < b64_decoded_size(in) || len % 4 != 0)
return 0;
for (i = 0; i < len; i++) {
if (!b64_isvalidchar(in[i])) {
return 0;
}
}
for (i = 0, j = 0; i < len; i += 4, j += 3) {
v = b64invs[in[i] - 43];
v = (v << 6) | b64invs[in[i + 1] - 43];
v = in[i + 2] == '=' ? v << 6 : (v << 6) | b64invs[in[i + 2] - 43];
v = in[i + 3] == '=' ? v << 6 : (v << 6) | b64invs[in[i + 3] - 43];
out[j] = (v >> 16) & 0xFF;
if (in[i + 2] != '=')
out[j + 1] = (v >> 8) & 0xFF;
if (in[i + 3] != '=')
out[j + 2] = v & 0xFF;
}
return 1;
}

View File

@ -1,20 +0,0 @@
#pragma once
#include <stdint.h>
#include <stdlib.h>
// based on https://nachtimwald.com/2017/11/18/base64-encode-and-decode-in-c/
#ifdef __cplusplus
extern "C" {
#endif
size_t b64_encoded_size(size_t inlen);
char *b64_encode(const uint8_t *in, size_t len);
size_t b64_decoded_size(const char *in);
int b64_decode(const char *in, uint8_t *out, size_t outlen);
#ifdef __cplusplus
}
#endif

View File

@ -37,6 +37,16 @@ CXXFLAGS := $(CFLAGS)
ASFLAGS := -g $(ARCH)
LDFLAGS = -g $(ARCH) $(RPXSPECS) -Wl,-Map,$(notdir $*.map) $(WUPSSPECS)
ifeq ($(DEBUG),1)
CXXFLAGS += -DDEBUG -g
CFLAGS += -DDEBUG -g
endif
ifeq ($(DEBUG),VERBOSE)
CXXFLAGS += -DDEBUG -DVERBOSE_DEBUG -g
CFLAGS += -DDEBUG -DVERBOSE_DEBUG -g
endif
LIBS := -lwups -lwut
#-------------------------------------------------------------------------------
@ -96,7 +106,7 @@ export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
all: $(BUILD)
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@$(shell [ ! -d $(BUILD) ] && mkdir -p $(BUILD))
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
#-------------------------------------------------------------------------------

View File

@ -0,0 +1,234 @@
#include "utils/logger.h"
#include <coreinit/filesystem.h>
#include <malloc.h>
#include <stdbool.h>
#include <stdio.h>
#include <wups.h>
#include <wups/config/WUPSConfigItemBoolean.h>
#include <wups/config/WUPSConfigItemMultipleValues.h>
#include <wups/config/WUPSConfigItemStub.h>
/**
Mandatory plugin information.
If not set correctly, the loader will refuse to use the plugin.
**/
WUPS_PLUGIN_NAME("Example plugin");
WUPS_PLUGIN_DESCRIPTION("This is just an example plugin and will log the FSOpenFile function.");
WUPS_PLUGIN_VERSION("v1.0");
WUPS_PLUGIN_AUTHOR("Maschell");
WUPS_PLUGIN_LICENSE("BSD");
#define LOG_FS_OPEN_CONFIG_ID "logFSOpen"
/**
All of this defines can be used in ANY file.
It's possible to split it up into multiple files.
**/
WUPS_USE_WUT_DEVOPTAB(); // Use the wut devoptabs
WUPS_USE_STORAGE("example_plugin"); // Unique id for the storage api
bool logFSOpen = true;
/**
* Callback that will be called if the config has been changed
*/
void logFSOpenChanged(ConfigItemBoolean *item, bool newValue) {
DEBUG_FUNCTION_LINE_INFO("New value in logFSOpenChanged: %d", newValue);
logFSOpen = newValue;
// If the value has changed, we store it in the storage.
WUPSStorageAPI_StoreBool(NULL, LOG_FS_OPEN_CONFIG_ID, logFSOpen);
}
WUPSConfigAPICallbackStatus ConfigMenuOpenedCallback(WUPSConfigCategoryHandle root) {
{
// Let's create a new category called "Settings"
WUPSConfigCategoryHandle settingsCategory;
WUPSConfigAPICreateCategoryOptionsV1 settingsCategoryOptions = {.name = "Settings"};
if (WUPSConfigAPI_Category_Create(settingsCategoryOptions, &settingsCategory) != WUPSCONFIG_API_RESULT_SUCCESS) {
DEBUG_FUNCTION_LINE_ERR("Failed to create settings category");
return WUPSCONFIG_API_CALLBACK_RESULT_ERROR;
}
// Add a new item to this settings category
if (WUPSConfigItemBoolean_AddToCategory(settingsCategory, LOG_FS_OPEN_CONFIG_ID, "Log FSOpen calls", true, logFSOpen, &logFSOpenChanged) != WUPSCONFIG_API_RESULT_SUCCESS) {
DEBUG_FUNCTION_LINE_ERR("Failed to add item to category");
return WUPSCONFIG_API_CALLBACK_RESULT_ERROR;
}
// Add the category to the root.
if (WUPSConfigAPI_Category_AddCategory(root, settingsCategory) != WUPSCONFIG_API_RESULT_SUCCESS) {
DEBUG_FUNCTION_LINE_ERR("Failed to add category to root item");
}
}
{
// We can also have categories inside categories!
WUPSConfigCategoryHandle categoryLevel1;
WUPSConfigAPICreateCategoryOptionsV1 catLev1Options = {.name = "Category with subcategory"};
if (WUPSConfigAPI_Category_Create(catLev1Options, &categoryLevel1) != WUPSCONFIG_API_RESULT_SUCCESS) {
DEBUG_FUNCTION_LINE_ERR("Failed to create categoryLevel1");
return WUPSCONFIG_API_CALLBACK_RESULT_ERROR;
}
WUPSConfigCategoryHandle categoryLevel2;
WUPSConfigAPICreateCategoryOptionsV1 catLev2Options = {.name = "Category inside category"};
if (WUPSConfigAPI_Category_Create(catLev2Options, &categoryLevel2) != WUPSCONFIG_API_RESULT_SUCCESS) {
DEBUG_FUNCTION_LINE_ERR("Failed to create categoryLevel1");
return WUPSCONFIG_API_CALLBACK_RESULT_ERROR;
}
if (WUPSConfigItemBoolean_AddToCategory(categoryLevel2, "stubInsideCategory", "This is stub item inside a nested category", false, false, NULL) != WUPSCONFIG_API_RESULT_SUCCESS) {
DEBUG_FUNCTION_LINE_ERR("Failed to add stub item to root category");
return WUPSCONFIG_API_CALLBACK_RESULT_ERROR;
}
// add categoryLevel2 to categoryLevel1
if (WUPSConfigAPI_Category_AddCategory(categoryLevel1, categoryLevel2) != WUPSCONFIG_API_RESULT_SUCCESS) {
DEBUG_FUNCTION_LINE_ERR("Failed to add category to root item");
return WUPSCONFIG_API_CALLBACK_RESULT_ERROR;
}
// add categoryLevel2 to categoryLevel1
if (WUPSConfigAPI_Category_AddCategory(root, categoryLevel1) != WUPSCONFIG_API_RESULT_SUCCESS) {
DEBUG_FUNCTION_LINE_ERR("Failed to add category to root item");
return WUPSCONFIG_API_CALLBACK_RESULT_ERROR;
}
}
{
// We can also directly add items to the root category
if (WUPSConfigItemStub_AddToCategory(root, "This is stub item without category") != WUPSCONFIG_API_RESULT_SUCCESS) {
DEBUG_FUNCTION_LINE_ERR("Failed to add stub item to root category");
return WUPSCONFIG_API_CALLBACK_RESULT_ERROR;
}
ConfigItemMultipleValuesPair values[10];
int numOfElements = sizeof(values) / sizeof(values[0]);
for (int i = 0; i < numOfElements; i++) {
#define STR_SIZE 10
char *str = (char *) malloc(STR_SIZE);
if (!str) {
OSFatal("Failed to allocate memory");
}
snprintf(str, STR_SIZE, "%d", i);
values[i].value = i;
values[i].valueName = str;
}
WUPSConfigAPIStatus multValuesRes = WUPSConfigItemMultipleValues_AddToCategory(root, "multival", "Multiple values", 0, 0, values, numOfElements, NULL);
for (int i = 0; i < sizeof(values) / sizeof(values[0]); i++) {
free((void *) values[i].valueName);
}
if (multValuesRes != WUPSCONFIG_API_RESULT_SUCCESS) {
return WUPSCONFIG_API_CALLBACK_RESULT_ERROR;
}
}
return WUPSCONFIG_API_CALLBACK_RESULT_SUCCESS;
}
void ConfigMenuClosedCallback() {
WUPSStorageAPI_SaveStorage(false);
}
/**
Gets called ONCE when the plugin was loaded.
**/
INITIALIZE_PLUGIN() {
// Logging only works when compiled with `make DEBUG=1`. See the README for more information.
initLogging();
DEBUG_FUNCTION_LINE("INITIALIZE_PLUGIN of example_plugin!");
WUPSConfigAPIOptionsV1 configOptions = {.name = "example_plugin"};
if (WUPSConfigAPI_Init(configOptions, ConfigMenuOpenedCallback, ConfigMenuClosedCallback) != WUPSCONFIG_API_RESULT_SUCCESS) {
DEBUG_FUNCTION_LINE_ERR("Failed to init config api");
}
WUPSStorageError storageRes;
// Try to get value from storage
if ((storageRes = WUPSStorageAPI_GetBool(NULL, LOG_FS_OPEN_CONFIG_ID, &logFSOpen)) == WUPS_STORAGE_ERROR_NOT_FOUND) {
// Add the value to the storage if it's missing.
if (WUPSStorageAPI_StoreBool(NULL, LOG_FS_OPEN_CONFIG_ID, logFSOpen) != WUPS_STORAGE_ERROR_SUCCESS) {
DEBUG_FUNCTION_LINE_ERR("Failed to store bool");
}
} else if (storageRes != WUPS_STORAGE_ERROR_SUCCESS) {
DEBUG_FUNCTION_LINE_ERR("Failed to get bool %s (%d)", WUPSConfigAPI_GetStatusStr(storageRes), storageRes);
} else {
DEBUG_FUNCTION_LINE_ERR("Successfully read the value from storage: %d %s (%d)", logFSOpen, WUPSConfigAPI_GetStatusStr(storageRes), storageRes);
}
WUPSStorageAPI_SaveStorage(false);
deinitLogging();
}
/**
Gets called when the plugin will be unloaded.
**/
DEINITIALIZE_PLUGIN() {
DEBUG_FUNCTION_LINE("DEINITIALIZE_PLUGIN of example_plugin!");
}
/**
Gets called when an application starts.
**/
ON_APPLICATION_START() {
initLogging();
DEBUG_FUNCTION_LINE("ON_APPLICATION_START of example_plugin!");
}
/**
* Gets called when an application actually ends
*/
ON_APPLICATION_ENDS() {
deinitLogging();
}
/**
Gets called when an application request to exit.
**/
ON_APPLICATION_REQUESTS_EXIT() {
DEBUG_FUNCTION_LINE_INFO("ON_APPLICATION_REQUESTS_EXIT of example_plugin!");
}
/**
This defines a function replacement.
It allows to replace the system function with an own function.
So whenever a game / application calls an overridden function, your function gets called instead.
Currently it's only possible to override functions that are loaded from .rpl files of OSv10 (00050010-1000400A).
Signature of this macro:
DECL_FUNCTION( RETURN_TYPE, ARBITRARY_NAME_OF_FUNCTION , ARGS_SEPERATED_BY_COMMA){
//Your code goes here.
}
Within this macro, two more function get declare you can use.
my_ARBITRARY_NAME_OF_FUNCTION and real_ARBITRARY_NAME_OF_FUNCTION
RETURN_TYPE my_ARBITRARY_NAME_OF_FUNCTION(ARGS_SEPERATED_BY_COMMA):
is just name of the function that gets declared in this macro.
It has the same effect as calling the overridden function directly.
RETURN_TYPE real_ARBITRARY_NAME_OF_FUNCTION(ARGS_SEPERATED_BY_COMMA):
is the name of the function, that leads to function that was overridden.
Use this to call the original function that will be overridden.
CAUTION: Other plugins may already have manipulated the return value or arguments.
Use this macro for each function you want to override
**/
DECL_FUNCTION(int, FSOpenFile, FSClient *pClient, FSCmdBlock *pCmd, const char *path, const char *mode, int *handle, int error) {
int result = real_FSOpenFile(pClient, pCmd, path, mode, handle, error);
if (logFSOpen) {
DEBUG_FUNCTION_LINE_INFO("FSOpenFile called for folder %s! Result %d", path, result);
}
return result;
}
/**
This tells the loader which functions from which library (.rpl) should be replaced with which function from this file.
The list of possible libraries can be found in the wiki. (In general it's WUPS_LOADER_LIBRARY_ + the name of the RPL in caps lock)
WUPS_MUST_REPLACE(FUNCTION_NAME_IN_THIS_FILE, NAME_OF_LIB_WHICH_CONTAINS_THIS_FUNCTION, NAME_OF_FUNCTION_TO_OVERRIDE)
Define this for each function you want to override.
**/
WUPS_MUST_REPLACE(FSOpenFile, WUPS_LOADER_LIBRARY_COREINIT, FSOpenFile);

View File

@ -1,169 +0,0 @@
#include "utils/logger.h"
#include <coreinit/filesystem.h>
#include <malloc.h>
#include <string.h>
#include <wups.h>
#include <wups/config/WUPSConfigItemBoolean.h>
/**
Mandatory plugin information.
If not set correctly, the loader will refuse to use the plugin.
**/
WUPS_PLUGIN_NAME("Example plugin");
WUPS_PLUGIN_DESCRIPTION("This is just an example plugin and will log the FSOpenFile function.");
WUPS_PLUGIN_VERSION("v1.0");
WUPS_PLUGIN_AUTHOR("Maschell");
WUPS_PLUGIN_LICENSE("BSD");
#define LOG_FS_OPEN_CONFIG_ID "logFSOpen"
/**
All of this defines can be used in ANY file.
It's possible to split it up into multiple files.
**/
WUPS_USE_WUT_DEVOPTAB(); // Use the wut devoptabs
WUPS_USE_STORAGE("example_plugin"); // Unique id for the storage api
bool logFSOpen = true;
/**
Gets called ONCE when the plugin was loaded.
**/
INITIALIZE_PLUGIN() {
// Logging only works when compiled with `make DEBUG=1`. See the README for more information.
initLogging();
DEBUG_FUNCTION_LINE("INITIALIZE_PLUGIN of example_plugin!");
// Open storage to read values
WUPSStorageError storageRes = WUPS_OpenStorage();
if (storageRes != WUPS_STORAGE_ERROR_SUCCESS) {
DEBUG_FUNCTION_LINE("Failed to open storage %s (%d)", WUPS_GetStorageStatusStr(storageRes), storageRes);
} else {
// Try to get value from storage
if ((storageRes = WUPS_GetBool(nullptr, LOG_FS_OPEN_CONFIG_ID, &logFSOpen)) == WUPS_STORAGE_ERROR_NOT_FOUND) {
// Add the value to the storage if it's missing.
if (WUPS_StoreBool(nullptr, LOG_FS_OPEN_CONFIG_ID, logFSOpen) != WUPS_STORAGE_ERROR_SUCCESS) {
DEBUG_FUNCTION_LINE("Failed to store bool");
}
} else if (storageRes != WUPS_STORAGE_ERROR_SUCCESS) {
DEBUG_FUNCTION_LINE("Failed to get bool %s (%d)", WUPS_GetStorageStatusStr(storageRes), storageRes);
}
// Close storage
if (WUPS_CloseStorage() != WUPS_STORAGE_ERROR_SUCCESS) {
DEBUG_FUNCTION_LINE("Failed to close storage");
}
}
deinitLogging();
}
/**
Gets called when the plugin will be unloaded.
**/
DEINITIALIZE_PLUGIN() {
DEBUG_FUNCTION_LINE("DEINITIALIZE_PLUGIN of example_plugin!");
}
/**
Gets called when an application starts.
**/
ON_APPLICATION_START() {
initLogging();
DEBUG_FUNCTION_LINE("ON_APPLICATION_START of example_plugin!");
}
/**
* Gets called when an application actually ends
*/
ON_APPLICATION_ENDS() {
deinitLogging();
}
/**
Gets called when an application request to exit.
**/
ON_APPLICATION_REQUESTS_EXIT() {
DEBUG_FUNCTION_LINE_INFO("ON_APPLICATION_REQUESTS_EXIT of example_plugin!");
}
/**
* Callback that will be called if the config has been changed
*/
void logFSOpenChanged(ConfigItemBoolean *item, bool newValue) {
DEBUG_FUNCTION_LINE_INFO("New value in logFSOpenChanged: %d", newValue);
logFSOpen = newValue;
// If the value has changed, we store it in the storage.
WUPS_StoreInt(nullptr, LOG_FS_OPEN_CONFIG_ID, logFSOpen);
}
WUPS_GET_CONFIG() {
// We open the storage, so we can persist the configuration the user did.
if (WUPS_OpenStorage() != WUPS_STORAGE_ERROR_SUCCESS) {
DEBUG_FUNCTION_LINE("Failed to open storage");
return 0;
}
WUPSConfigHandle config;
WUPSConfig_CreateHandled(&config, "Example Plugin");
WUPSConfigCategoryHandle cat;
WUPSConfig_AddCategoryByNameHandled(config, "Logging", &cat);
WUPSConfigItemBoolean_AddToCategoryHandled(config, cat, LOG_FS_OPEN_CONFIG_ID, "Log FSOpen calls", logFSOpen, &logFSOpenChanged);
return config;
}
WUPS_CONFIG_CLOSED() {
// Save all changes
if (WUPS_CloseStorage() != WUPS_STORAGE_ERROR_SUCCESS) {
DEBUG_FUNCTION_LINE_ERR("Failed to close storage");
}
}
/**
This defines a function replacement.
It allows to replace the system function with an own function.
So whenever a game / application calls an overridden function, your function gets called instead.
Currently it's only possible to override functions that are loaded from .rpl files of OSv10 (00050010-1000400A).
Signature of this macro:
DECL_FUNCTION( RETURN_TYPE, ARBITRARY_NAME_OF_FUNCTION , ARGS_SEPERATED_BY_COMMA){
//Your code goes here.
}
Within this macro, two more function get declare you can use.
my_ARBITRARY_NAME_OF_FUNCTION and real_ARBITRARY_NAME_OF_FUNCTION
RETURN_TYPE my_ARBITRARY_NAME_OF_FUNCTION(ARGS_SEPERATED_BY_COMMA):
is just name of the function that gets declared in this macro.
It has the same effect as calling the overridden function directly.
RETURN_TYPE real_ARBITRARY_NAME_OF_FUNCTION(ARGS_SEPERATED_BY_COMMA):
is the name of the function, that leads to function that was overridden.
Use this to call the original function that will be overridden.
CAUTION: Other plugins may already have manipulated the return value or arguments.
Use this macro for each function you want to override
**/
DECL_FUNCTION(int, FSOpenFile, FSClient *pClient, FSCmdBlock *pCmd, const char *path, const char *mode, int *handle, int error) {
int result = real_FSOpenFile(pClient, pCmd, path, mode, handle, error);
if (logFSOpen) {
DEBUG_FUNCTION_LINE_INFO("FSOpenFile called for folder %s! Result %d", path, result);
}
return result;
}
/**
This tells the loader which functions from which library (.rpl) should be replaced with which function from this file.
The list of possible libraries can be found in the wiki. (In general it's WUPS_LOADER_LIBRARY_ + the name of the RPL in caps lock)
WUPS_MUST_REPLACE(FUNCTION_NAME_IN_THIS_FILE, NAME_OF_LIB_WHICH_CONTAINS_THIS_FUNCTION, NAME_OF_FUNCTION_TO_OVERRIDE)
Define this for each function you want to override.
**/
WUPS_MUST_REPLACE(FSOpenFile, WUPS_LOADER_LIBRARY_COREINIT, FSOpenFile);

View File

@ -0,0 +1,67 @@
# Generated from CLion C/C++ Code Style settings
BasedOnStyle: LLVM
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: Consecutive
AlignConsecutiveMacros: AcrossEmptyLinesAndComments
AlignOperands: Align
AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Always
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: Yes
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: true
BreakBeforeBinaryOperators: None
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
ColumnLimit: 0
CompactNamespaces: false
ContinuationIndentWidth: 8
IndentCaseLabels: true
IndentPPDirectives: None
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: true
MaxEmptyLinesToKeep: 2
NamespaceIndentation: All
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PointerAlignment: Right
ReflowComments: false
SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
TabWidth: 4
UseTab: Never

View File

@ -0,0 +1,5 @@
FROM ghcr.io/wiiu-env/devkitppc:20230218
COPY --from=ghcr.io/wiiu-env/wiiupluginsystem:20230215 /artifacts $DEVKITPRO
WORKDIR project

View File

@ -0,0 +1,135 @@
#-------------------------------------------------------------------------------
.SUFFIXES:
#-------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITPRO)),)
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro")
endif
TOPDIR ?= $(CURDIR)
include $(DEVKITPRO)/wups/share/wups_rules
WUT_ROOT := $(DEVKITPRO)/wut
#-------------------------------------------------------------------------------
# TARGET is the name of the output
# BUILD is the directory where object files & intermediate files will be placed
# SOURCES is a list of directories containing source code
# DATA is a list of directories containing data files
# INCLUDES is a list of directories containing header files
#-------------------------------------------------------------------------------
TARGET := ExamplePluginCPP
BUILD := build
SOURCES := src src/utils
DATA := data
INCLUDES := src
#-------------------------------------------------------------------------------
# options for code generation
#-------------------------------------------------------------------------------
CFLAGS := -g -Wall -O2 -ffunction-sections \
$(MACHDEP)
CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__ -D__WUPS__
CXXFLAGS := $(CFLAGS) -std=c++20
ASFLAGS := -g $(ARCH)
LDFLAGS = -g $(ARCH) $(RPXSPECS) -Wl,-Map,$(notdir $*.map) $(WUPSSPECS)
LIBS := -lwups -lwut
#-------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level
# containing include and lib
#-------------------------------------------------------------------------------
LIBDIRS := $(PORTLIBS) $(WUPS_ROOT) $(WUT_ROOT)
#-------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#-------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR)))
#-------------------------------------------------------------------------------
export OUTPUT := $(CURDIR)/$(TARGET)
export TOPDIR := $(CURDIR)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
#-------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#-------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#-------------------------------------------------------------------------------
export LD := $(CC)
#-------------------------------------------------------------------------------
else
#-------------------------------------------------------------------------------
export LD := $(CXX)
#-------------------------------------------------------------------------------
endif
#-------------------------------------------------------------------------------
export OFILES_BIN := $(addsuffix .o,$(BINFILES))
export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export OFILES := $(OFILES_BIN) $(OFILES_SRC)
export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES)))
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
.PHONY: $(BUILD) clean all
#-------------------------------------------------------------------------------
all: $(BUILD)
$(BUILD):
@$(shell [ ! -d $(BUILD) ] && mkdir -p $(BUILD))
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
#-------------------------------------------------------------------------------
clean:
@echo clean ...
@rm -fr $(BUILD) $(TARGET).wps $(TARGET).elf
#-------------------------------------------------------------------------------
else
.PHONY: all
DEPENDS := $(OFILES:.o=.d)
#-------------------------------------------------------------------------------
# main targets
#-------------------------------------------------------------------------------
all : $(OUTPUT).wps
$(OUTPUT).wps : $(OUTPUT).elf
$(OUTPUT).elf : $(OFILES)
$(OFILES_SRC) : $(HFILES_BIN)
#-------------------------------------------------------------------------------
# you need a rule like this for each extension you use as binary data
#-------------------------------------------------------------------------------
%.bin.o %_bin.h : %.bin
#-------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
-include $(DEPENDS)
#-------------------------------------------------------------------------------
endif
#-------------------------------------------------------------------------------

View File

@ -0,0 +1,57 @@
# Example plugin
This is just a simple example plugin which can be used as a template.
The plugin logs the FSOpenFile calls via UDP (**Only when build via `make DEBUG=1`**).
The logging can be enabled/disabled via the WUPS Config menu (press L, DPAD Down and Minus on the GamePad, Pro Controller or Classic Controller).
## Installation
(`[ENVIRONMENT]` is a placeholder for the actual environment name.)
1. Copy the file `ExamplePlugin.wps` into `sd:/wiiu/environments/[ENVIRONMENT]/plugins`.
2. Requires the [WiiUPluginLoaderBackend](https://github.com/wiiu-env/WiiUPluginLoaderBackend) in `sd:/wiiu/environments/[ENVIRONMENT]/modules`.
Start the environment (e.g Aroma) and the backend should load the plugin.
## Building
For building you need:
- [wups](https://github.com/Maschell/WiiUPluginSystem)
- [wut](https://github.com/devkitpro/wut)
Install them (in this order) according to their README's. Don't forget the dependencies of the libs itself.
Then you should be able to compile via `make` (with no logging) or `make DEBUG=1` (with logging).
## Buildflags
### Logging
Building via `make` only logs errors (via OSReport). To enable logging via the [LoggingModule](https://github.com/wiiu-env/LoggingModule) set `DEBUG` to `1` or `VERBOSE`.
`make` Logs errors only (via OSReport).
`make DEBUG=1` Enables information and error logging via [LoggingModule](https://github.com/wiiu-env/LoggingModule).
`make DEBUG=VERBOSE` Enables verbose information and error logging via [LoggingModule](https://github.com/wiiu-env/LoggingModule).
If the [LoggingModule](https://github.com/wiiu-env/LoggingModule) is not present, it'll fallback to UDP (Port 4405) and [CafeOS](https://github.com/wiiu-env/USBSerialLoggingModule) logging.
## Building using the Dockerfile
It's possible to use a docker image for building. This way you don't need anything installed on your host system.
```
# Build docker image (only needed once)
docker build . -t example-plugin-builder
# make
docker run -it --rm -v ${PWD}:/project example-plugin-builder make DEBUG=1
# make clean
docker run -it --rm -v ${PWD}:/project example-plugin-builder make clean
```
## Format the code via docker
`docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./src -i`

View File

@ -0,0 +1,286 @@
#include "utils/logger.h"
#include <coreinit/filesystem.h>
#include <malloc.h>
#include <wups.h>
#include <wups/config/WUPSConfigItemBoolean.h>
#include <wups/config/WUPSConfigItemIntegerRange.h>
#include <wups/config/WUPSConfigItemMultipleValues.h>
#include <wups/config/WUPSConfigItemStub.h>
#include <wups/config_api.h>
/**
Mandatory plugin information.
If not set correctly, the loader will refuse to use the plugin.
**/
WUPS_PLUGIN_NAME("Example plugin C++");
WUPS_PLUGIN_DESCRIPTION("This is just an example plugin written in C++");
WUPS_PLUGIN_VERSION("v1.0");
WUPS_PLUGIN_AUTHOR("Maschell");
WUPS_PLUGIN_LICENSE("BSD");
#define LOG_FS_OPEN_CONFIG_ID "logFSOpen"
#define OTHER_EXAMPLE_BOOL_CONFIG_ID "otherBoolItem"
#define OTHER_EXAMPLE2_BOOL_CONFIG_ID "other2BoolItem"
#define INTEGER_RANGE_EXAMPLE_CONFIG_ID "intRangeExample"
#define MULTIPLE_VALUES_EXAMPLE_CONFIG_ID "multValueExample"
/**
All of this defines can be used in ANY file.
It's possible to split it up into multiple files.
**/
WUPS_USE_WUT_DEVOPTAB(); // Use the wut devoptabs
WUPS_USE_STORAGE("example_plugin_cpp"); // Unique id for the storage api
enum ExampleOptions {
EXAMPLE_OPTION_1 = 0,
EXAMPLE_OPTION_2 = 1,
EXAMPLE_OPTION_3 = 2,
};
#define LOF_FS_OPEN_DEFAULT_VALUE true
#define INTEGER_RANGE_DEFAULT_VALUE 10
#define MULTIPLE_VALUES_DEFAULT_VALUE EXAMPLE_OPTION_2
bool sLogFSOpen = LOF_FS_OPEN_DEFAULT_VALUE;
int sIntegerRangeValue = INTEGER_RANGE_DEFAULT_VALUE;
ExampleOptions sExampleOptionValue = MULTIPLE_VALUES_DEFAULT_VALUE;
/**
* Callback that will be called if the config has been changed
*/
void boolItemChanged(ConfigItemBoolean *item, bool newValue) {
DEBUG_FUNCTION_LINE_INFO("New value in boolItemChanged: %d", newValue);
if (std::string_view(LOG_FS_OPEN_CONFIG_ID) == item->identifier) {
sLogFSOpen = newValue;
// If the value has changed, we store it in the storage.
WUPSStorageAPI::Store(item->identifier, newValue);
} else if (std::string_view(OTHER_EXAMPLE_BOOL_CONFIG_ID) == item->identifier) {
DEBUG_FUNCTION_LINE_ERR("Other bool value has changed to %d", newValue);
} else if (std::string_view(OTHER_EXAMPLE2_BOOL_CONFIG_ID) == item->identifier) {
DEBUG_FUNCTION_LINE_ERR("Other2 bool value has changed to %d", newValue);
}
}
void integerRangeItemChanged(ConfigItemIntegerRange *item, int newValue) {
DEBUG_FUNCTION_LINE_INFO("New value in integerRangeItemChanged: %d", newValue);
// If the value has changed, we store it in the storage.
if (std::string_view(LOG_FS_OPEN_CONFIG_ID) == item->identifier) {
sIntegerRangeValue = newValue;
// If the value has changed, we store it in the storage.
WUPSStorageAPI::Store(item->identifier, newValue);
}
}
void multipleValueItemChanged(ConfigItemIntegerRange *item, uint32_t newValue) {
DEBUG_FUNCTION_LINE_INFO("New value in multipleValueItemChanged: %d", newValue);
// If the value has changed, we store it in the storage.
if (std::string_view(MULTIPLE_VALUES_EXAMPLE_CONFIG_ID) == item->identifier) {
sExampleOptionValue = (ExampleOptions) newValue;
// If the value has changed, we store it in the storage.
WUPSStorageAPI::Store(item->identifier, sExampleOptionValue);
}
}
WUPSConfigAPICallbackStatus ConfigMenuOpenedCallback(WUPSConfigCategoryHandle rootHandle) {
// To use the C++ API, we create new WUPSConfigCategory from the root handle!
WUPSConfigCategory root = WUPSConfigCategory(rootHandle);
// The functions of the Config API come in two variants: One that throws an exception, and another one which doesn't
// To use the Config API without exception see the example below this try/catch block.
try {
// Then we can simply create a new category
auto functionPatchesCat = WUPSConfigCategory::Create("function patches");
// Add a boolean item to this newly created category
functionPatchesCat.add(WUPSConfigItemBoolean::Create(LOG_FS_OPEN_CONFIG_ID, "Log FSOpen calls",
LOF_FS_OPEN_DEFAULT_VALUE, sLogFSOpen,
boolItemChanged));
// And finally move that category to the root category.
// Note: "functionPatchesCat" can NOT be changed after adding it to root.
root.add(std::move(functionPatchesCat));
// We can also add items directly to root!
root.add(WUPSConfigItemBoolean::Create(OTHER_EXAMPLE_BOOL_CONFIG_ID, "Just another bool item",
false, false,
boolItemChanged));
// You can also add an item which just displays any text.
root.add(WUPSConfigItemStub::Create("This item is just displaying some text"));
// It's also possible to create and item to select an integer from a range.
root.add(WUPSConfigItemIntegerRange::Create(INTEGER_RANGE_EXAMPLE_CONFIG_ID, "Item for selecting an integer between 0 and 50",
INTEGER_RANGE_DEFAULT_VALUE, sIntegerRangeValue,
0, 50,
&integerRangeItemChanged));
// To select value from an enum WUPSConfigItemMultipleValues fits the best.
constexpr WUPSConfigItemMultipleValues::ValuePair possibleValues[] = {
{EXAMPLE_OPTION_1, "Option 1"},
{EXAMPLE_OPTION_2, "Option 2"},
{EXAMPLE_OPTION_3, "Option 3"},
};
// It comes in two variants.
// - "WUPSConfigItemMultipleValues::CreateFromValue" will take a default and current **value**
// - "WUPSConfigItemMultipleValues::CreateFromIndex" will take a default and current **index**
root.add(WUPSConfigItemMultipleValues::CreateFromValue(MULTIPLE_VALUES_EXAMPLE_CONFIG_ID, "Select an option!",
MULTIPLE_VALUES_DEFAULT_VALUE, sExampleOptionValue,
possibleValues,
nullptr));
// It's also possible to have nested categories
auto nc1 = WUPSConfigCategory::Create("Category inside root");
auto nc2 = WUPSConfigCategory::Create("Category inside subcategory 1");
auto nc3 = WUPSConfigCategory::Create("Category inside subcategory 2");
nc3.add(WUPSConfigItemStub::Create("Item inside subcategory 3"));
nc2.add(WUPSConfigItemStub::Create("Item inside subcategory 2"));
nc1.add(WUPSConfigItemStub::Create("Item inside subcategory 1"));
nc2.add(std::move(nc3));
nc1.add(std::move(nc2));
root.add(std::move(nc1));
} catch (std::exception &e) {
DEBUG_FUNCTION_LINE_ERR("Creating config menu failed: %s", e.what());
return WUPSCONFIG_API_CALLBACK_RESULT_ERROR;
}
// In case we don't like exception, we can use the API as well.
// If we add a "WUPSConfigAPIStatus" reference to the API calls, the function won't throw an exception.
// Instead it will return std::optionals and write the result into the WUPSConfigAPIStatus.
WUPSConfigAPIStatus err;
auto categoryOpt = WUPSConfigCategory::Create("Just another Category", err);
if (!categoryOpt) {
DEBUG_FUNCTION_LINE_ERR("Failed to create category: %s", WUPSConfigAPI_GetStatusStr(err));
return WUPSCONFIG_API_CALLBACK_RESULT_ERROR;
}
auto boolItemOpt = WUPSConfigItemBoolean::Create(OTHER_EXAMPLE2_BOOL_CONFIG_ID, "Just another bool item",
false, false,
boolItemChanged,
err);
if (!boolItemOpt) {
DEBUG_FUNCTION_LINE_ERR("Failed to create bool item: %s", WUPSConfigAPI_GetStatusStr(err));
return WUPSCONFIG_API_CALLBACK_RESULT_ERROR;
}
// Add bool item to category
if (!categoryOpt->add(std::move(*boolItemOpt), err)) {
DEBUG_FUNCTION_LINE_ERR("Failed to add bool item to category: %s", WUPSConfigAPI_GetStatusStr(err));
return WUPSCONFIG_API_CALLBACK_RESULT_ERROR;
}
// Add category to root.
if (!root.add(std::move(*categoryOpt), err)) {
DEBUG_FUNCTION_LINE_ERR("Failed to add category to root: %s", WUPSConfigAPI_GetStatusStr(err));
return WUPSCONFIG_API_CALLBACK_RESULT_ERROR;
}
return WUPSCONFIG_API_CALLBACK_RESULT_SUCCESS;
}
void ConfigMenuClosedCallback() {
WUPSStorageAPI::SaveStorage();
}
/**
Gets called ONCE when the plugin was loaded.
**/
INITIALIZE_PLUGIN() {
// Logging only works when compiled with `make DEBUG=1`. See the README for more information.
initLogging();
DEBUG_FUNCTION_LINE("INITIALIZE_PLUGIN of example_plugin!");
WUPSConfigAPIOptionsV1 configOptions = {.name = "example_plugin_cpp"};
if (WUPSConfigAPI_Init(configOptions, ConfigMenuOpenedCallback, ConfigMenuClosedCallback) != WUPSCONFIG_API_RESULT_SUCCESS) {
DEBUG_FUNCTION_LINE_ERR("Failed to init config api");
}
WUPSStorageError storageRes;
if ((storageRes = WUPSStorageAPI::GetOrStoreDefault(LOG_FS_OPEN_CONFIG_ID, sLogFSOpen, LOF_FS_OPEN_DEFAULT_VALUE)) != WUPS_STORAGE_ERROR_SUCCESS) {
DEBUG_FUNCTION_LINE_ERR("GetOrStoreDefault failed: %s (%d)", WUPSStorageAPI_GetStatusStr(storageRes), storageRes);
}
if ((storageRes = WUPSStorageAPI::SaveStorage()) != WUPS_STORAGE_ERROR_SUCCESS) {
DEBUG_FUNCTION_LINE_ERR("GetOrStoreDefault failed: %s (%d)", WUPSStorageAPI_GetStatusStr(storageRes), storageRes);
}
deinitLogging();
}
/**
Gets called when the plugin will be unloaded.
**/
DEINITIALIZE_PLUGIN() {
DEBUG_FUNCTION_LINE("DEINITIALIZE_PLUGIN of example_plugin!");
}
/**
Gets called when an application starts.
**/
ON_APPLICATION_START() {
initLogging();
DEBUG_FUNCTION_LINE("ON_APPLICATION_START of example_plugin!");
}
/**
* Gets called when an application actually ends
*/
ON_APPLICATION_ENDS() {
deinitLogging();
}
/**
Gets called when an application request to exit.
**/
ON_APPLICATION_REQUESTS_EXIT() {
DEBUG_FUNCTION_LINE_INFO("ON_APPLICATION_REQUESTS_EXIT of example_plugin!");
}
/**
This defines a function replacement.
It allows to replace the system function with an own function.
So whenever a game / application calls an overridden function, your function gets called instead.
Currently it's only possible to override functions that are loaded from .rpl files of OSv10 (00050010-1000400A).
Signature of this macro:
DECL_FUNCTION( RETURN_TYPE, ARBITRARY_NAME_OF_FUNCTION , ARGS_SEPERATED_BY_COMMA){
//Your code goes here.
}
Within this macro, two more function get declare you can use.
my_ARBITRARY_NAME_OF_FUNCTION and real_ARBITRARY_NAME_OF_FUNCTION
RETURN_TYPE my_ARBITRARY_NAME_OF_FUNCTION(ARGS_SEPERATED_BY_COMMA):
is just name of the function that gets declared in this macro.
It has the same effect as calling the overridden function directly.
RETURN_TYPE real_ARBITRARY_NAME_OF_FUNCTION(ARGS_SEPERATED_BY_COMMA):
is the name of the function, that leads to function that was overridden.
Use this to call the original function that will be overridden.
CAUTION: Other plugins may already have manipulated the return value or arguments.
Use this macro for each function you want to override
**/
DECL_FUNCTION(int, FSOpenFile, FSClient *pClient, FSCmdBlock *pCmd, const char *path, const char *mode, int *handle, int error) {
int result = real_FSOpenFile(pClient, pCmd, path, mode, handle, error);
if (sLogFSOpen) {
DEBUG_FUNCTION_LINE_INFO("FSOpenFile called for folder %s! Result %d", path, result);
}
return result;
}
/**
This tells the loader which functions from which library (.rpl) should be replaced with which function from this file.
The list of possible libraries can be found in the wiki. (In general it's WUPS_LOADER_LIBRARY_ + the name of the RPL in caps lock)
WUPS_MUST_REPLACE(FUNCTION_NAME_IN_THIS_FILE, NAME_OF_LIB_WHICH_CONTAINS_THIS_FUNCTION, NAME_OF_FUNCTION_TO_OVERRIDE)
Define this for each function you want to override.
**/
WUPS_MUST_REPLACE(FSOpenFile, WUPS_LOADER_LIBRARY_COREINIT, FSOpenFile);

View File

@ -0,0 +1,36 @@
#ifdef DEBUG
#include <stdint.h>
#include <whb/log_cafe.h>
#include <whb/log_module.h>
#include <whb/log_udp.h>
uint32_t moduleLogInit = false;
uint32_t cafeLogInit = false;
uint32_t udpLogInit = false;
#endif // DEBUG
void initLogging() {
#ifdef DEBUG
if (!(moduleLogInit = WHBLogModuleInit())) {
cafeLogInit = WHBLogCafeInit();
udpLogInit = WHBLogUdpInit();
}
#endif // DEBUG
}
void deinitLogging() {
#ifdef DEBUG
if (moduleLogInit) {
WHBLogModuleDeinit();
moduleLogInit = false;
}
if (cafeLogInit) {
WHBLogCafeDeinit();
cafeLogInit = false;
}
if (udpLogInit) {
WHBLogUdpDeinit();
udpLogInit = false;
}
#endif // DEBUG
}

View File

@ -0,0 +1,74 @@
#pragma once
#include <coreinit/debug.h>
#include <string.h>
#include <whb/log.h>
#ifdef __cplusplus
extern "C" {
#endif
#define LOG_APP_TYPE "P"
#define LOG_APP_NAME "ExamplePlugin"
#define __FILENAME__ ({ \
const char *__filename = __FILE__; \
const char *__pos = strrchr(__filename, '/'); \
if (!__pos) __pos = strrchr(__filename, '\\'); \
__pos ? __pos + 1 : __filename; \
})
#define LOG(LOG_FUNC, FMT, ARGS...) LOG_EX_DEFAULT(LOG_FUNC, "", "", FMT, ##ARGS)
#define LOG_EX_DEFAULT(LOG_FUNC, LOG_LEVEL, LINE_END, FMT, ARGS...) LOG_EX(__FILENAME__, __FUNCTION__, __LINE__, LOG_FUNC, LOG_LEVEL, LINE_END, FMT, ##ARGS)
#define LOG_EX(FILENAME, FUNCTION, LINE, LOG_FUNC, LOG_LEVEL, LINE_END, FMT, ARGS...) \
do { \
LOG_FUNC("[(%s)%18s][%23s]%30s@L%04d: " LOG_LEVEL "" FMT "" LINE_END, LOG_APP_TYPE, LOG_APP_NAME, FILENAME, FUNCTION, LINE, ##ARGS); \
} while (0)
#ifdef DEBUG
#ifdef VERBOSE_DEBUG
#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) LOG(WHBLogPrintf, FMT, ##ARGS)
#define DEBUG_FUNCTION_LINE_VERBOSE_EX(FILENAME, FUNCTION, LINE, FMT, ARGS...) LOG_EX(FILENAME, FUNCTION, LINE, WHBLogPrintf, "", "", FMT, ##ARGS);
#else
#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) while (0)
#define DEBUG_FUNCTION_LINE_VERBOSE_EX(FMT, ARGS...) while (0)
#endif
#define DEBUG_FUNCTION_LINE(FMT, ARGS...) LOG(WHBLogPrintf, FMT, ##ARGS)
#define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...) LOG(WHBLogWritef, FMT, ##ARGS)
#define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX_DEFAULT(WHBLogPrintf, "##ERROR## ", "", FMT, ##ARGS)
#define DEBUG_FUNCTION_LINE_WARN(FMT, ARGS...) LOG_EX_DEFAULT(WHBLogPrintf, "##WARN ## ", "", FMT, ##ARGS)
#define DEBUG_FUNCTION_LINE_INFO(FMT, ARGS...) LOG_EX_DEFAULT(WHBLogPrintf, "##INFO ## ", "", FMT, ##ARGS)
#define DEBUG_FUNCTION_LINE_ERR_LAMBDA(FILENAME, FUNCTION, LINE, FMT, ARGS...) LOG_EX(FILENAME, FUNCTION, LINE, WHBLogPrintf, "##ERROR## ", "", FMT, ##ARGS);
#else
#define DEBUG_FUNCTION_LINE_VERBOSE_EX(FMT, ARGS...) while (0)
#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) while (0)
#define DEBUG_FUNCTION_LINE(FMT, ARGS...) while (0)
#define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...) while (0)
#define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX_DEFAULT(OSReport, "##ERROR## ", "\n", FMT, ##ARGS)
#define DEBUG_FUNCTION_LINE_WARN(FMT, ARGS...) LOG_EX_DEFAULT(OSReport, "##WARN ## ", "\n", FMT, ##ARGS)
#define DEBUG_FUNCTION_LINE_INFO(FMT, ARGS...) LOG_EX_DEFAULT(OSReport, "##INFO ## ", "\n", FMT, ##ARGS)
#define DEBUG_FUNCTION_LINE_ERR_LAMBDA(FILENAME, FUNCTION, LINE, FMT, ARGS...) LOG_EX(FILENAME, FUNCTION, LINE, OSReport, "##ERROR## ", "\n", FMT, ##ARGS);
#endif
void initLogging();
void deinitLogging();
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,67 @@
# Generated from CLion C/C++ Code Style settings
BasedOnStyle: LLVM
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: Consecutive
AlignConsecutiveMacros: AcrossEmptyLinesAndComments
AlignOperands: Align
AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Always
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: Yes
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: true
BreakBeforeBinaryOperators: None
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
ColumnLimit: 0
CompactNamespaces: false
ContinuationIndentWidth: 8
IndentCaseLabels: true
IndentPPDirectives: None
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: true
MaxEmptyLinesToKeep: 2
NamespaceIndentation: All
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PointerAlignment: Right
ReflowComments: false
SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
TabWidth: 4
UseTab: Never

View File

@ -0,0 +1,5 @@
FROM ghcr.io/wiiu-env/devkitppc:20230218
COPY --from=ghcr.io/wiiu-env/wiiupluginsystem:20230215 /artifacts $DEVKITPRO
WORKDIR project

View File

@ -0,0 +1,143 @@
#-------------------------------------------------------------------------------
.SUFFIXES:
#-------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITPRO)),)
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro")
endif
TOPDIR ?= $(CURDIR)
include $(DEVKITPRO)/wups/share/wups_rules
WUT_ROOT := $(DEVKITPRO)/wut
#-------------------------------------------------------------------------------
# TARGET is the name of the output
# BUILD is the directory where object files & intermediate files will be placed
# SOURCES is a list of directories containing source code
# DATA is a list of directories containing data files
# INCLUDES is a list of directories containing header files
#-------------------------------------------------------------------------------
TARGET := StorageTestPlugin
BUILD := build
SOURCES := src src/utils \
src/catch2 \
src/catch2/benchmark \
src/catch2/generators \
src/catch2/interfaces \
src/catch2/internal \
src/catch2/matchers \
src/catch2/matchers/internal \
src/catch2/reporters \
DATA := data
INCLUDES := src
#-------------------------------------------------------------------------------
# options for code generation
#-------------------------------------------------------------------------------
CFLAGS := -g -Wall -O2 -ffunction-sections \
$(MACHDEP)
CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__ -D__WUPS__
CXXFLAGS := $(CFLAGS)
ASFLAGS := -g $(ARCH)
LDFLAGS = -g $(ARCH) $(RPXSPECS) -Wl,-Map,$(notdir $*.map) $(WUPSSPECS)
LIBS := -lwups -lwut
#-------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level
# containing include and lib
#-------------------------------------------------------------------------------
LIBDIRS := $(PORTLIBS) $(WUPS_ROOT) $(WUT_ROOT)
#-------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#-------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR)))
#-------------------------------------------------------------------------------
export OUTPUT := $(CURDIR)/$(TARGET)
export TOPDIR := $(CURDIR)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
#-------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#-------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#-------------------------------------------------------------------------------
export LD := $(CC)
#-------------------------------------------------------------------------------
else
#-------------------------------------------------------------------------------
export LD := $(CXX)
#-------------------------------------------------------------------------------
endif
#-------------------------------------------------------------------------------
export OFILES_BIN := $(addsuffix .o,$(BINFILES))
export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export OFILES := $(OFILES_BIN) $(OFILES_SRC)
export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES)))
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
.PHONY: $(BUILD) clean all
#-------------------------------------------------------------------------------
all: $(BUILD)
$(BUILD):
@$(shell [ ! -d $(BUILD) ] && mkdir -p $(BUILD))
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
#-------------------------------------------------------------------------------
clean:
@echo clean ...
@rm -fr $(BUILD) $(TARGET).wps $(TARGET).elf
#-------------------------------------------------------------------------------
else
.PHONY: all
DEPENDS := $(OFILES:.o=.d)
#-------------------------------------------------------------------------------
# main targets
#-------------------------------------------------------------------------------
all : $(OUTPUT).wps
$(OUTPUT).wps : $(OUTPUT).elf
$(OUTPUT).elf : $(OFILES)
$(OFILES_SRC) : $(HFILES_BIN)
#-------------------------------------------------------------------------------
# you need a rule like this for each extension you use as binary data
#-------------------------------------------------------------------------------
%.bin.o %_bin.h : %.bin
#-------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
-include $(DEPENDS)
#-------------------------------------------------------------------------------
endif
#-------------------------------------------------------------------------------

View File

@ -0,0 +1,55 @@
# Storage test plugin
This plugin implements several tests of StorageAPI. Results are be printed to the OSConsole ([USBSerialLoggingModule](https://github.com/wiiu-env/USBSerialLoggingModule))
## Installation
(`[ENVIRONMENT]` is a placeholder for the actual environment name.)
1. Requires the [WiiUPluginLoaderBackend](https://github.com/wiiu-env/WiiUPluginLoaderBackend) in `sd:/wiiu/environments/[ENVIRONMENT]/modules`.
It's recommended to [wiiload](https://github.com/wiiu-env/wiiload_plugin) this plugin whenever you want to run the test.
This plugin uses [Catch2](https://github.com/catchorg/Catch2) to run tests.
## Building
For building you need:
- [wups](https://github.com/Maschell/WiiUPluginSystem)
- [wut](https://github.com/devkitpro/wut)
Install them (in this order) according to their README's. Don't forget the dependencies of the libs itself.
Then you should be able to compile via `make` (with no logging) or `make DEBUG=1` (with logging).
## Buildflags
### Logging
Building via `make` only logs errors (via OSReport). To enable logging via the [LoggingModule](https://github.com/wiiu-env/LoggingModule) set `DEBUG` to `1` or `VERBOSE`.
`make` Logs errors only (via OSReport).
`make DEBUG=1` Enables information and error logging via [LoggingModule](https://github.com/wiiu-env/LoggingModule).
`make DEBUG=VERBOSE` Enables verbose information and error logging via [LoggingModule](https://github.com/wiiu-env/LoggingModule).
If the [LoggingModule](https://github.com/wiiu-env/LoggingModule) isn't present, it will fall back to UDP (port 4405) and [CafeOS](https://github.com/wiiu-env/USBSerialLoggingModule) logging.
## Building using the Dockerfile
It's possible to use a docker image for building. This way you don't need anything installed on your host system.
```
# Build docker image (only needed once)
docker build . -t storage-test-plugin-builder
# make
docker run -it --rm -v ${PWD}:/project storage-test-plugin-builder make DEBUG=1
# make clean
docker run -it --rm -v ${PWD}:/project storage-test-plugin-builder make clean
```
## Format the code via docker
`docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./src --exclude ./src/catch2 -i`

View File

@ -0,0 +1,148 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
// Adapted from donated nonius code.
#ifndef CATCH_BENCHMARK_HPP_INCLUDED
#define CATCH_BENCHMARK_HPP_INCLUDED
#include <catch2/catch_user_config.hpp>
#include <catch2/internal/catch_compiler_capabilities.hpp>
#include <catch2/internal/catch_context.hpp>
#include <catch2/internal/catch_move_and_forward.hpp>
#include <catch2/internal/catch_test_failure_exception.hpp>
#include <catch2/internal/catch_unique_name.hpp>
#include <catch2/interfaces/catch_interfaces_capture.hpp>
#include <catch2/interfaces/catch_interfaces_config.hpp>
#include <catch2/interfaces/catch_interfaces_registry_hub.hpp>
#include <catch2/benchmark/detail/catch_benchmark_stats.hpp>
#include <catch2/benchmark/catch_clock.hpp>
#include <catch2/benchmark/catch_environment.hpp>
#include <catch2/benchmark/catch_execution_plan.hpp>
#include <catch2/benchmark/detail/catch_estimate_clock.hpp>
#include <catch2/benchmark/detail/catch_analyse.hpp>
#include <catch2/benchmark/detail/catch_benchmark_function.hpp>
#include <catch2/benchmark/detail/catch_run_for_at_least.hpp>
#include <algorithm>
#include <chrono>
#include <exception>
#include <functional>
#include <string>
#include <vector>
#include <cmath>
namespace Catch {
namespace Benchmark {
struct Benchmark {
Benchmark(std::string&& benchmarkName)
: name(CATCH_MOVE(benchmarkName)) {}
template <class FUN>
Benchmark(std::string&& benchmarkName , FUN &&func)
: fun(CATCH_MOVE(func)), name(CATCH_MOVE(benchmarkName)) {}
template <typename Clock>
ExecutionPlan<FloatDuration<Clock>> prepare(const IConfig &cfg, Environment<FloatDuration<Clock>> env) const {
auto min_time = env.clock_resolution.mean * Detail::minimum_ticks;
auto run_time = std::max(min_time, std::chrono::duration_cast<decltype(min_time)>(cfg.benchmarkWarmupTime()));
auto&& test = Detail::run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(run_time), 1, fun);
int new_iters = static_cast<int>(std::ceil(min_time * test.iterations / test.elapsed));
return { new_iters, test.elapsed / test.iterations * new_iters * cfg.benchmarkSamples(), fun, std::chrono::duration_cast<FloatDuration<Clock>>(cfg.benchmarkWarmupTime()), Detail::warmup_iterations };
}
template <typename Clock = default_clock>
void run() {
auto const* cfg = getCurrentContext().getConfig();
auto env = Detail::measure_environment<Clock>();
getResultCapture().benchmarkPreparing(name);
CATCH_TRY{
auto plan = user_code([&] {
return prepare<Clock>(*cfg, env);
});
BenchmarkInfo info {
CATCH_MOVE(name),
plan.estimated_duration.count(),
plan.iterations_per_sample,
cfg->benchmarkSamples(),
cfg->benchmarkResamples(),
env.clock_resolution.mean.count(),
env.clock_cost.mean.count()
};
getResultCapture().benchmarkStarting(info);
auto samples = user_code([&] {
return plan.template run<Clock>(*cfg, env);
});
auto analysis = Detail::analyse(*cfg, env, samples.begin(), samples.end());
BenchmarkStats<FloatDuration<Clock>> stats{ CATCH_MOVE(info), CATCH_MOVE(analysis.samples), analysis.mean, analysis.standard_deviation, analysis.outliers, analysis.outlier_variance };
getResultCapture().benchmarkEnded(stats);
} CATCH_CATCH_ANON (TestFailureException const&) {
getResultCapture().benchmarkFailed("Benchmark failed due to failed assertion"_sr);
} CATCH_CATCH_ALL{
getResultCapture().benchmarkFailed(translateActiveException());
// We let the exception go further up so that the
// test case is marked as failed.
std::rethrow_exception(std::current_exception());
}
}
// sets lambda to be used in fun *and* executes benchmark!
template <typename Fun, std::enable_if_t<!Detail::is_related<Fun, Benchmark>::value, int> = 0>
Benchmark & operator=(Fun func) {
auto const* cfg = getCurrentContext().getConfig();
if (!cfg->skipBenchmarks()) {
fun = Detail::BenchmarkFunction(func);
run();
}
return *this;
}
explicit operator bool() {
return true;
}
private:
Detail::BenchmarkFunction fun;
std::string name;
};
}
} // namespace Catch
#define INTERNAL_CATCH_GET_1_ARG(arg1, arg2, ...) arg1
#define INTERNAL_CATCH_GET_2_ARG(arg1, arg2, ...) arg2
#define INTERNAL_CATCH_BENCHMARK(BenchmarkName, name, benchmarkIndex)\
if( Catch::Benchmark::Benchmark BenchmarkName{name} ) \
BenchmarkName = [&](int benchmarkIndex)
#define INTERNAL_CATCH_BENCHMARK_ADVANCED(BenchmarkName, name)\
if( Catch::Benchmark::Benchmark BenchmarkName{name} ) \
BenchmarkName = [&]
#if defined(CATCH_CONFIG_PREFIX_ALL)
#define CATCH_BENCHMARK(...) \
INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,))
#define CATCH_BENCHMARK_ADVANCED(name) \
INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), name)
#else
#define BENCHMARK(...) \
INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,))
#define BENCHMARK_ADVANCED(name) \
INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), name)
#endif
#endif // CATCH_BENCHMARK_HPP_INCLUDED

View File

@ -0,0 +1,46 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
/** \file
* This is a convenience header for Catch2's benchmarking. It includes
* **all** of Catch2 headers related to benchmarking.
*
* Generally the Catch2 users should use specific includes they need,
* but this header can be used instead for ease-of-experimentation, or
* just plain convenience, at the cost of (significantly) increased
* compilation times.
*
* When a new header is added to either the `benchmark` folder, or to
* the corresponding internal (detail) subfolder, it should be added here.
*/
#ifndef CATCH_BENCHMARK_ALL_HPP_INCLUDED
#define CATCH_BENCHMARK_ALL_HPP_INCLUDED
#include <catch2/benchmark/catch_benchmark.hpp>
#include <catch2/benchmark/catch_chronometer.hpp>
#include <catch2/benchmark/catch_clock.hpp>
#include <catch2/benchmark/catch_constructor.hpp>
#include <catch2/benchmark/catch_environment.hpp>
#include <catch2/benchmark/catch_estimate.hpp>
#include <catch2/benchmark/catch_execution_plan.hpp>
#include <catch2/benchmark/catch_optimizer.hpp>
#include <catch2/benchmark/catch_outlier_classification.hpp>
#include <catch2/benchmark/catch_sample_analysis.hpp>
#include <catch2/benchmark/detail/catch_analyse.hpp>
#include <catch2/benchmark/detail/catch_benchmark_function.hpp>
#include <catch2/benchmark/detail/catch_benchmark_stats.hpp>
#include <catch2/benchmark/detail/catch_benchmark_stats_fwd.hpp>
#include <catch2/benchmark/detail/catch_complete_invoke.hpp>
#include <catch2/benchmark/detail/catch_estimate_clock.hpp>
#include <catch2/benchmark/detail/catch_measure.hpp>
#include <catch2/benchmark/detail/catch_repeat.hpp>
#include <catch2/benchmark/detail/catch_run_for_at_least.hpp>
#include <catch2/benchmark/detail/catch_stats.hpp>
#include <catch2/benchmark/detail/catch_timing.hpp>
#endif // CATCH_BENCHMARK_ALL_HPP_INCLUDED

View File

@ -0,0 +1,17 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/benchmark/catch_chronometer.hpp>
namespace Catch {
namespace Benchmark {
namespace Detail {
ChronometerConcept::~ChronometerConcept() = default;
} // namespace Detail
} // namespace Benchmark
} // namespace Catch

View File

@ -0,0 +1,74 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
// Adapted from donated nonius code.
#ifndef CATCH_CHRONOMETER_HPP_INCLUDED
#define CATCH_CHRONOMETER_HPP_INCLUDED
#include <catch2/benchmark/catch_clock.hpp>
#include <catch2/benchmark/catch_optimizer.hpp>
#include <catch2/internal/catch_meta.hpp>
#include <catch2/internal/catch_move_and_forward.hpp>
namespace Catch {
namespace Benchmark {
namespace Detail {
struct ChronometerConcept {
virtual void start() = 0;
virtual void finish() = 0;
virtual ~ChronometerConcept(); // = default;
ChronometerConcept() = default;
ChronometerConcept(ChronometerConcept const&) = default;
ChronometerConcept& operator=(ChronometerConcept const&) = default;
};
template <typename Clock>
struct ChronometerModel final : public ChronometerConcept {
void start() override { started = Clock::now(); }
void finish() override { finished = Clock::now(); }
ClockDuration<Clock> elapsed() const { return finished - started; }
TimePoint<Clock> started;
TimePoint<Clock> finished;
};
} // namespace Detail
struct Chronometer {
public:
template <typename Fun>
void measure(Fun&& fun) { measure(CATCH_FORWARD(fun), is_callable<Fun(int)>()); }
int runs() const { return repeats; }
Chronometer(Detail::ChronometerConcept& meter, int repeats_)
: impl(&meter)
, repeats(repeats_) {}
private:
template <typename Fun>
void measure(Fun&& fun, std::false_type) {
measure([&fun](int) { return fun(); }, std::true_type());
}
template <typename Fun>
void measure(Fun&& fun, std::true_type) {
Detail::optimizer_barrier();
impl->start();
for (int i = 0; i < repeats; ++i) invoke_deoptimized(fun, i);
impl->finish();
Detail::optimizer_barrier();
}
Detail::ChronometerConcept* impl;
int repeats;
};
} // namespace Benchmark
} // namespace Catch
#endif // CATCH_CHRONOMETER_HPP_INCLUDED

View File

@ -0,0 +1,39 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
// Adapted from donated nonius code.
#ifndef CATCH_CLOCK_HPP_INCLUDED
#define CATCH_CLOCK_HPP_INCLUDED
#include <chrono>
#include <ratio>
namespace Catch {
namespace Benchmark {
template <typename Clock>
using ClockDuration = typename Clock::duration;
template <typename Clock>
using FloatDuration = std::chrono::duration<double, typename Clock::period>;
template <typename Clock>
using TimePoint = typename Clock::time_point;
using default_clock = std::chrono::steady_clock;
template <typename Clock>
struct now {
TimePoint<Clock> operator()() const {
return Clock::now();
}
};
using fp_seconds = std::chrono::duration<double, std::ratio<1>>;
} // namespace Benchmark
} // namespace Catch
#endif // CATCH_CLOCK_HPP_INCLUDED

View File

@ -0,0 +1,82 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
// Adapted from donated nonius code.
#ifndef CATCH_CONSTRUCTOR_HPP_INCLUDED
#define CATCH_CONSTRUCTOR_HPP_INCLUDED
#include <catch2/internal/catch_move_and_forward.hpp>
#include <type_traits>
namespace Catch {
namespace Benchmark {
namespace Detail {
template <typename T, bool Destruct>
struct ObjectStorage
{
ObjectStorage() = default;
ObjectStorage(const ObjectStorage& other)
{
new(&data) T(other.stored_object());
}
ObjectStorage(ObjectStorage&& other)
{
new(data) T(CATCH_MOVE(other.stored_object()));
}
~ObjectStorage() { destruct_on_exit<T>(); }
template <typename... Args>
void construct(Args&&... args)
{
new (data) T(CATCH_FORWARD(args)...);
}
template <bool AllowManualDestruction = !Destruct>
std::enable_if_t<AllowManualDestruction> destruct()
{
stored_object().~T();
}
private:
// If this is a constructor benchmark, destruct the underlying object
template <typename U>
void destruct_on_exit(std::enable_if_t<Destruct, U>* = nullptr) { destruct<true>(); }
// Otherwise, don't
template <typename U>
void destruct_on_exit(std::enable_if_t<!Destruct, U>* = nullptr) { }
#if defined( __GNUC__ ) && __GNUC__ <= 6
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wstrict-aliasing"
#endif
T& stored_object() { return *reinterpret_cast<T*>( data ); }
T const& stored_object() const {
return *reinterpret_cast<T const*>( data );
}
#if defined( __GNUC__ ) && __GNUC__ <= 6
# pragma GCC diagnostic pop
#endif
alignas( T ) unsigned char data[sizeof( T )]{};
};
} // namespace Detail
template <typename T>
using storage_for = Detail::ObjectStorage<T, true>;
template <typename T>
using destructable_object = Detail::ObjectStorage<T, false>;
} // namespace Benchmark
} // namespace Catch
#endif // CATCH_CONSTRUCTOR_HPP_INCLUDED

View File

@ -0,0 +1,37 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
// Adapted from donated nonius code.
#ifndef CATCH_ENVIRONMENT_HPP_INCLUDED
#define CATCH_ENVIRONMENT_HPP_INCLUDED
#include <catch2/benchmark/catch_clock.hpp>
#include <catch2/benchmark/catch_outlier_classification.hpp>
namespace Catch {
namespace Benchmark {
template <typename Duration>
struct EnvironmentEstimate {
Duration mean;
OutlierClassification outliers;
template <typename Duration2>
operator EnvironmentEstimate<Duration2>() const {
return { mean, outliers };
}
};
template <typename Clock>
struct Environment {
using clock_type = Clock;
EnvironmentEstimate<FloatDuration<Clock>> clock_resolution;
EnvironmentEstimate<FloatDuration<Clock>> clock_cost;
};
} // namespace Benchmark
} // namespace Catch
#endif // CATCH_ENVIRONMENT_HPP_INCLUDED

View File

@ -0,0 +1,30 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
// Adapted from donated nonius code.
#ifndef CATCH_ESTIMATE_HPP_INCLUDED
#define CATCH_ESTIMATE_HPP_INCLUDED
namespace Catch {
namespace Benchmark {
template <typename Duration>
struct Estimate {
Duration point;
Duration lower_bound;
Duration upper_bound;
double confidence_interval;
template <typename Duration2>
operator Estimate<Duration2>() const {
return { point, lower_bound, upper_bound, confidence_interval };
}
};
} // namespace Benchmark
} // namespace Catch
#endif // CATCH_ESTIMATE_HPP_INCLUDED

View File

@ -0,0 +1,60 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
// Adapted from donated nonius code.
#ifndef CATCH_EXECUTION_PLAN_HPP_INCLUDED
#define CATCH_EXECUTION_PLAN_HPP_INCLUDED
#include <catch2/interfaces/catch_interfaces_config.hpp>
#include <catch2/benchmark/catch_clock.hpp>
#include <catch2/benchmark/catch_environment.hpp>
#include <catch2/benchmark/detail/catch_benchmark_function.hpp>
#include <catch2/benchmark/detail/catch_repeat.hpp>
#include <catch2/benchmark/detail/catch_run_for_at_least.hpp>
#include <vector>
namespace Catch {
namespace Benchmark {
template <typename Duration>
struct ExecutionPlan {
int iterations_per_sample;
Duration estimated_duration;
Detail::BenchmarkFunction benchmark;
Duration warmup_time;
int warmup_iterations;
template <typename Duration2>
operator ExecutionPlan<Duration2>() const {
return { iterations_per_sample, estimated_duration, benchmark, warmup_time, warmup_iterations };
}
template <typename Clock>
std::vector<FloatDuration<Clock>> run(const IConfig &cfg, Environment<FloatDuration<Clock>> env) const {
// warmup a bit
Detail::run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(warmup_time), warmup_iterations, Detail::repeat(now<Clock>{}));
std::vector<FloatDuration<Clock>> times;
const auto num_samples = cfg.benchmarkSamples();
times.reserve( num_samples );
for ( size_t i = 0; i < num_samples; ++i ) {
Detail::ChronometerModel<Clock> model;
this->benchmark( Chronometer( model, iterations_per_sample ) );
auto sample_time = model.elapsed() - env.clock_cost.mean;
if ( sample_time < FloatDuration<Clock>::zero() ) {
sample_time = FloatDuration<Clock>::zero();
}
times.push_back(sample_time / iterations_per_sample);
}
return times;
}
};
} // namespace Benchmark
} // namespace Catch
#endif // CATCH_EXECUTION_PLAN_HPP_INCLUDED

View File

@ -0,0 +1,78 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
// Adapted from donated nonius code.
#ifndef CATCH_OPTIMIZER_HPP_INCLUDED
#define CATCH_OPTIMIZER_HPP_INCLUDED
#if defined(_MSC_VER) || defined(__IAR_SYSTEMS_ICC__)
# include <atomic> // atomic_thread_fence
#endif
#include <catch2/internal/catch_move_and_forward.hpp>
#include <type_traits>
namespace Catch {
namespace Benchmark {
#if defined(__GNUC__) || defined(__clang__)
template <typename T>
inline void keep_memory(T* p) {
asm volatile("" : : "g"(p) : "memory");
}
inline void keep_memory() {
asm volatile("" : : : "memory");
}
namespace Detail {
inline void optimizer_barrier() { keep_memory(); }
} // namespace Detail
#elif defined(_MSC_VER) || defined(__IAR_SYSTEMS_ICC__)
#if defined(_MSVC_VER)
#pragma optimize("", off)
#elif defined(__IAR_SYSTEMS_ICC__)
// For IAR the pragma only affects the following function
#pragma optimize=disable
#endif
template <typename T>
inline void keep_memory(T* p) {
// thanks @milleniumbug
*reinterpret_cast<char volatile*>(p) = *reinterpret_cast<char const volatile*>(p);
}
// TODO equivalent keep_memory()
#if defined(_MSVC_VER)
#pragma optimize("", on)
#endif
namespace Detail {
inline void optimizer_barrier() {
std::atomic_thread_fence(std::memory_order_seq_cst);
}
} // namespace Detail
#endif
template <typename T>
inline void deoptimize_value(T&& x) {
keep_memory(&x);
}
template <typename Fn, typename... Args>
inline auto invoke_deoptimized(Fn&& fn, Args&&... args) -> std::enable_if_t<!std::is_same<void, decltype(fn(args...))>::value> {
deoptimize_value(CATCH_FORWARD(fn) (CATCH_FORWARD(args)...));
}
template <typename Fn, typename... Args>
inline auto invoke_deoptimized(Fn&& fn, Args&&... args) -> std::enable_if_t<std::is_same<void, decltype(fn(args...))>::value> {
CATCH_FORWARD(fn) (CATCH_FORWARD(args)...);
}
} // namespace Benchmark
} // namespace Catch
#endif // CATCH_OPTIMIZER_HPP_INCLUDED

View File

@ -0,0 +1,29 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
// Adapted from donated nonius code.
#ifndef CATCH_OUTLIER_CLASSIFICATION_HPP_INCLUDED
#define CATCH_OUTLIER_CLASSIFICATION_HPP_INCLUDED
namespace Catch {
namespace Benchmark {
struct OutlierClassification {
int samples_seen = 0;
int low_severe = 0; // more than 3 times IQR below Q1
int low_mild = 0; // 1.5 to 3 times IQR below Q1
int high_mild = 0; // 1.5 to 3 times IQR above Q3
int high_severe = 0; // more than 3 times IQR above Q3
int total() const {
return low_severe + low_mild + high_mild + high_severe;
}
};
} // namespace Benchmark
} // namespace Catch
#endif // CATCH_OUTLIERS_CLASSIFICATION_HPP_INCLUDED

View File

@ -0,0 +1,48 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
// Adapted from donated nonius code.
#ifndef CATCH_SAMPLE_ANALYSIS_HPP_INCLUDED
#define CATCH_SAMPLE_ANALYSIS_HPP_INCLUDED
#include <catch2/benchmark/catch_estimate.hpp>
#include <catch2/benchmark/catch_outlier_classification.hpp>
#include <catch2/internal/catch_move_and_forward.hpp>
#include <vector>
namespace Catch {
namespace Benchmark {
template <typename Duration>
struct SampleAnalysis {
std::vector<Duration> samples;
Estimate<Duration> mean;
Estimate<Duration> standard_deviation;
OutlierClassification outliers;
double outlier_variance;
template <typename Duration2>
operator SampleAnalysis<Duration2>() const {
std::vector<Duration2> samples2;
samples2.reserve(samples.size());
for (auto const& d : samples) {
samples2.push_back(Duration2(d));
}
return {
CATCH_MOVE(samples2),
mean,
standard_deviation,
outliers,
outlier_variance,
};
}
};
} // namespace Benchmark
} // namespace Catch
#endif // CATCH_SAMPLE_ANALYSIS_HPP_INCLUDED

View File

@ -0,0 +1,82 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
// Adapted from donated nonius code.
#ifndef CATCH_ANALYSE_HPP_INCLUDED
#define CATCH_ANALYSE_HPP_INCLUDED
#include <catch2/benchmark/catch_environment.hpp>
#include <catch2/benchmark/catch_sample_analysis.hpp>
#include <catch2/benchmark/detail/catch_stats.hpp>
#include <catch2/interfaces/catch_interfaces_config.hpp>
#include <catch2/internal/catch_move_and_forward.hpp>
#include <vector>
namespace Catch {
namespace Benchmark {
namespace Detail {
template <typename Duration, typename Iterator>
SampleAnalysis<Duration> analyse(const IConfig &cfg, Environment<Duration>, Iterator first, Iterator last) {
if (!cfg.benchmarkNoAnalysis()) {
std::vector<double> samples;
samples.reserve(static_cast<size_t>(last - first));
for (auto current = first; current != last; ++current) {
samples.push_back( current->count() );
}
auto analysis = Catch::Benchmark::Detail::analyse_samples(cfg.benchmarkConfidenceInterval(), cfg.benchmarkResamples(), samples.begin(), samples.end());
auto outliers = Catch::Benchmark::Detail::classify_outliers(samples.begin(), samples.end());
auto wrap_estimate = [](Estimate<double> e) {
return Estimate<Duration> {
Duration(e.point),
Duration(e.lower_bound),
Duration(e.upper_bound),
e.confidence_interval,
};
};
std::vector<Duration> samples2;
samples2.reserve(samples.size());
for (auto s : samples) {
samples2.push_back( Duration( s ) );
}
return {
CATCH_MOVE(samples2),
wrap_estimate(analysis.mean),
wrap_estimate(analysis.standard_deviation),
outliers,
analysis.outlier_variance,
};
} else {
std::vector<Duration> samples;
samples.reserve(static_cast<size_t>(last - first));
Duration mean = Duration(0);
int i = 0;
for (auto it = first; it < last; ++it, ++i) {
samples.push_back(Duration(*it));
mean += Duration(*it);
}
mean /= i;
return {
CATCH_MOVE(samples),
Estimate<Duration>{mean, mean, mean, 0.0},
Estimate<Duration>{Duration(0), Duration(0), Duration(0), 0.0},
OutlierClassification{},
0.0
};
}
}
} // namespace Detail
} // namespace Benchmark
} // namespace Catch
#endif // CATCH_ANALYSE_HPP_INCLUDED

View File

@ -0,0 +1,17 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/benchmark/detail/catch_benchmark_function.hpp>
namespace Catch {
namespace Benchmark {
namespace Detail {
BenchmarkFunction::callable::~callable() = default;
} // namespace Detail
} // namespace Benchmark
} // namespace Catch

View File

@ -0,0 +1,107 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
// Adapted from donated nonius code.
#ifndef CATCH_BENCHMARK_FUNCTION_HPP_INCLUDED
#define CATCH_BENCHMARK_FUNCTION_HPP_INCLUDED
#include <catch2/benchmark/catch_chronometer.hpp>
#include <catch2/internal/catch_meta.hpp>
#include <catch2/internal/catch_unique_ptr.hpp>
#include <catch2/internal/catch_move_and_forward.hpp>
#include <type_traits>
namespace Catch {
namespace Benchmark {
namespace Detail {
template <typename T, typename U>
struct is_related
: std::is_same<std::decay_t<T>, std::decay_t<U>> {};
/// We need to reinvent std::function because every piece of code that might add overhead
/// in a measurement context needs to have consistent performance characteristics so that we
/// can account for it in the measurement.
/// Implementations of std::function with optimizations that aren't always applicable, like
/// small buffer optimizations, are not uncommon.
/// This is effectively an implementation of std::function without any such optimizations;
/// it may be slow, but it is consistently slow.
struct BenchmarkFunction {
private:
struct callable {
virtual void call(Chronometer meter) const = 0;
virtual Catch::Detail::unique_ptr<callable> clone() const = 0;
virtual ~callable(); // = default;
callable() = default;
callable(callable const&) = default;
callable& operator=(callable const&) = default;
};
template <typename Fun>
struct model : public callable {
model(Fun&& fun_) : fun(CATCH_MOVE(fun_)) {}
model(Fun const& fun_) : fun(fun_) {}
Catch::Detail::unique_ptr<callable> clone() const override {
return Catch::Detail::make_unique<model<Fun>>( *this );
}
void call(Chronometer meter) const override {
call(meter, is_callable<Fun(Chronometer)>());
}
void call(Chronometer meter, std::true_type) const {
fun(meter);
}
void call(Chronometer meter, std::false_type) const {
meter.measure(fun);
}
Fun fun;
};
struct do_nothing { void operator()() const {} };
template <typename T>
BenchmarkFunction(model<T>* c) : f(c) {}
public:
BenchmarkFunction()
: f(new model<do_nothing>{ {} }) {}
template <typename Fun,
std::enable_if_t<!is_related<Fun, BenchmarkFunction>::value, int> = 0>
BenchmarkFunction(Fun&& fun)
: f(new model<std::decay_t<Fun>>(CATCH_FORWARD(fun))) {}
BenchmarkFunction( BenchmarkFunction&& that ) noexcept:
f( CATCH_MOVE( that.f ) ) {}
BenchmarkFunction(BenchmarkFunction const& that)
: f(that.f->clone()) {}
BenchmarkFunction&
operator=( BenchmarkFunction&& that ) noexcept {
f = CATCH_MOVE( that.f );
return *this;
}
BenchmarkFunction& operator=(BenchmarkFunction const& that) {
f = that.f->clone();
return *this;
}
void operator()(Chronometer meter) const { f->call(meter); }
private:
Catch::Detail::unique_ptr<callable> f;
};
} // namespace Detail
} // namespace Benchmark
} // namespace Catch
#endif // CATCH_BENCHMARK_FUNCTION_HPP_INCLUDED

View File

@ -0,0 +1,64 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#ifndef CATCH_BENCHMARK_STATS_HPP_INCLUDED
#define CATCH_BENCHMARK_STATS_HPP_INCLUDED
#include <catch2/internal/catch_move_and_forward.hpp>
#include <catch2/benchmark/catch_estimate.hpp>
#include <catch2/benchmark/catch_outlier_classification.hpp>
// The fwd decl & default specialization needs to be seen by VS2017 before
// BenchmarkStats itself, or VS2017 will report compilation error.
#include <catch2/benchmark/detail/catch_benchmark_stats_fwd.hpp>
#include <string>
#include <vector>
namespace Catch {
struct BenchmarkInfo {
std::string name;
double estimatedDuration;
int iterations;
unsigned int samples;
unsigned int resamples;
double clockResolution;
double clockCost;
};
template <class Duration>
struct BenchmarkStats {
BenchmarkInfo info;
std::vector<Duration> samples;
Benchmark::Estimate<Duration> mean;
Benchmark::Estimate<Duration> standardDeviation;
Benchmark::OutlierClassification outliers;
double outlierVariance;
template <typename Duration2>
operator BenchmarkStats<Duration2>() const {
std::vector<Duration2> samples2;
samples2.reserve(samples.size());
for (auto const& sample : samples) {
samples2.push_back(Duration2(sample));
}
return {
info,
CATCH_MOVE(samples2),
mean,
standardDeviation,
outliers,
outlierVariance,
};
}
};
} // end namespace Catch
#endif // CATCH_BENCHMARK_STATS_HPP_INCLUDED

View File

@ -0,0 +1,23 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#ifndef CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED
#define CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED
#include <chrono>
namespace Catch {
// We cannot forward declare the type with default template argument
// multiple times, so it is split out into a separate header so that
// we can prevent multiple declarations in dependees
template <typename Duration = std::chrono::duration<double, std::nano>>
struct BenchmarkStats;
} // end namespace Catch
#endif // CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED

View File

@ -0,0 +1,58 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
// Adapted from donated nonius code.
#ifndef CATCH_COMPLETE_INVOKE_HPP_INCLUDED
#define CATCH_COMPLETE_INVOKE_HPP_INCLUDED
#include <catch2/internal/catch_meta.hpp>
#include <catch2/internal/catch_move_and_forward.hpp>
namespace Catch {
namespace Benchmark {
namespace Detail {
template <typename T>
struct CompleteType { using type = T; };
template <>
struct CompleteType<void> { struct type {}; };
template <typename T>
using CompleteType_t = typename CompleteType<T>::type;
template <typename Result>
struct CompleteInvoker {
template <typename Fun, typename... Args>
static Result invoke(Fun&& fun, Args&&... args) {
return CATCH_FORWARD(fun)(CATCH_FORWARD(args)...);
}
};
template <>
struct CompleteInvoker<void> {
template <typename Fun, typename... Args>
static CompleteType_t<void> invoke(Fun&& fun, Args&&... args) {
CATCH_FORWARD(fun)(CATCH_FORWARD(args)...);
return {};
}
};
// invoke and not return void :(
template <typename Fun, typename... Args>
CompleteType_t<FunctionReturnType<Fun, Args...>> complete_invoke(Fun&& fun, Args&&... args) {
return CompleteInvoker<FunctionReturnType<Fun, Args...>>::invoke(CATCH_FORWARD(fun), CATCH_FORWARD(args)...);
}
} // namespace Detail
template <typename Fun>
Detail::CompleteType_t<FunctionReturnType<Fun>> user_code(Fun&& fun) {
return Detail::complete_invoke(CATCH_FORWARD(fun));
}
} // namespace Benchmark
} // namespace Catch
#endif // CATCH_COMPLETE_INVOKE_HPP_INCLUDED

View File

@ -0,0 +1,125 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
// Adapted from donated nonius code.
#ifndef CATCH_ESTIMATE_CLOCK_HPP_INCLUDED
#define CATCH_ESTIMATE_CLOCK_HPP_INCLUDED
#include <catch2/benchmark/catch_clock.hpp>
#include <catch2/benchmark/catch_environment.hpp>
#include <catch2/benchmark/detail/catch_stats.hpp>
#include <catch2/benchmark/detail/catch_measure.hpp>
#include <catch2/benchmark/detail/catch_run_for_at_least.hpp>
#include <catch2/benchmark/catch_clock.hpp>
#include <catch2/internal/catch_unique_ptr.hpp>
#include <algorithm>
#include <vector>
#include <cmath>
namespace Catch {
namespace Benchmark {
namespace Detail {
template <typename Clock>
std::vector<double> resolution(int k) {
std::vector<TimePoint<Clock>> times;
times.reserve(static_cast<size_t>(k + 1));
for ( int i = 0; i < k + 1; ++i ) {
times.push_back( Clock::now() );
}
std::vector<double> deltas;
deltas.reserve(static_cast<size_t>(k));
for ( size_t idx = 1; idx < times.size(); ++idx ) {
deltas.push_back( static_cast<double>(
( times[idx] - times[idx - 1] ).count() ) );
}
return deltas;
}
constexpr auto warmup_iterations = 10000;
constexpr auto warmup_time = std::chrono::milliseconds(100);
constexpr auto minimum_ticks = 1000;
constexpr auto warmup_seed = 10000;
constexpr auto clock_resolution_estimation_time = std::chrono::milliseconds(500);
constexpr auto clock_cost_estimation_time_limit = std::chrono::seconds(1);
constexpr auto clock_cost_estimation_tick_limit = 100000;
constexpr auto clock_cost_estimation_time = std::chrono::milliseconds(10);
constexpr auto clock_cost_estimation_iterations = 10000;
template <typename Clock>
int warmup() {
return run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(warmup_time), warmup_seed, &resolution<Clock>)
.iterations;
}
template <typename Clock>
EnvironmentEstimate<FloatDuration<Clock>> estimate_clock_resolution(int iterations) {
auto r = run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(clock_resolution_estimation_time), iterations, &resolution<Clock>)
.result;
return {
FloatDuration<Clock>(mean(r.begin(), r.end())),
classify_outliers(r.begin(), r.end()),
};
}
template <typename Clock>
EnvironmentEstimate<FloatDuration<Clock>> estimate_clock_cost(FloatDuration<Clock> resolution) {
auto time_limit = (std::min)(
resolution * clock_cost_estimation_tick_limit,
FloatDuration<Clock>(clock_cost_estimation_time_limit));
auto time_clock = [](int k) {
return Detail::measure<Clock>([k] {
for (int i = 0; i < k; ++i) {
volatile auto ignored = Clock::now();
(void)ignored;
}
}).elapsed;
};
time_clock(1);
int iters = clock_cost_estimation_iterations;
auto&& r = run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(clock_cost_estimation_time), iters, time_clock);
std::vector<double> times;
int nsamples = static_cast<int>(std::ceil(time_limit / r.elapsed));
times.reserve(static_cast<size_t>(nsamples));
for ( int s = 0; s < nsamples; ++s ) {
times.push_back( static_cast<double>(
( time_clock( r.iterations ) / r.iterations )
.count() ) );
}
return {
FloatDuration<Clock>(mean(times.begin(), times.end())),
classify_outliers(times.begin(), times.end()),
};
}
template <typename Clock>
Environment<FloatDuration<Clock>> measure_environment() {
#if defined(__clang__)
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wexit-time-destructors"
#endif
static Catch::Detail::unique_ptr<Environment<FloatDuration<Clock>>> env;
#if defined(__clang__)
# pragma clang diagnostic pop
#endif
if (env) {
return *env;
}
auto iters = Detail::warmup<Clock>();
auto resolution = Detail::estimate_clock_resolution<Clock>(iters);
auto cost = Detail::estimate_clock_cost<Clock>(resolution.mean);
env = Catch::Detail::make_unique<Environment<FloatDuration<Clock>>>( Environment<FloatDuration<Clock>>{resolution, cost} );
return *env;
}
} // namespace Detail
} // namespace Benchmark
} // namespace Catch
#endif // CATCH_ESTIMATE_CLOCK_HPP_INCLUDED

View File

@ -0,0 +1,32 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
// Adapted from donated nonius code.
#ifndef CATCH_MEASURE_HPP_INCLUDED
#define CATCH_MEASURE_HPP_INCLUDED
#include <catch2/benchmark/detail/catch_complete_invoke.hpp>
#include <catch2/benchmark/detail/catch_timing.hpp>
#include <catch2/internal/catch_move_and_forward.hpp>
namespace Catch {
namespace Benchmark {
namespace Detail {
template <typename Clock, typename Fun, typename... Args>
TimingOf<Clock, Fun, Args...> measure(Fun&& fun, Args&&... args) {
auto start = Clock::now();
auto&& r = Detail::complete_invoke(fun, CATCH_FORWARD(args)...);
auto end = Clock::now();
auto delta = end - start;
return { delta, CATCH_FORWARD(r), 1 };
}
} // namespace Detail
} // namespace Benchmark
} // namespace Catch
#endif // CATCH_MEASURE_HPP_INCLUDED

View File

@ -0,0 +1,36 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
// Adapted from donated nonius code.
#ifndef CATCH_REPEAT_HPP_INCLUDED
#define CATCH_REPEAT_HPP_INCLUDED
#include <type_traits>
#include <catch2/internal/catch_move_and_forward.hpp>
namespace Catch {
namespace Benchmark {
namespace Detail {
template <typename Fun>
struct repeater {
void operator()(int k) const {
for (int i = 0; i < k; ++i) {
fun();
}
}
Fun fun;
};
template <typename Fun>
repeater<std::decay_t<Fun>> repeat(Fun&& fun) {
return { CATCH_FORWARD(fun) };
}
} // namespace Detail
} // namespace Benchmark
} // namespace Catch
#endif // CATCH_REPEAT_HPP_INCLUDED

View File

@ -0,0 +1,31 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/benchmark/detail/catch_run_for_at_least.hpp>
#include <catch2/internal/catch_enforce.hpp>
#include <exception>
namespace Catch {
namespace Benchmark {
namespace Detail {
struct optimized_away_error : std::exception {
const char* what() const noexcept override;
};
const char* optimized_away_error::what() const noexcept {
return "could not measure benchmark, maybe it was optimized away";
}
void throw_optimized_away_error() {
Catch::throw_exception(optimized_away_error{});
}
} // namespace Detail
} // namespace Benchmark
} // namespace Catch

View File

@ -0,0 +1,65 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
// Adapted from donated nonius code.
#ifndef CATCH_RUN_FOR_AT_LEAST_HPP_INCLUDED
#define CATCH_RUN_FOR_AT_LEAST_HPP_INCLUDED
#include <catch2/benchmark/catch_clock.hpp>
#include <catch2/benchmark/catch_chronometer.hpp>
#include <catch2/benchmark/detail/catch_measure.hpp>
#include <catch2/benchmark/detail/catch_complete_invoke.hpp>
#include <catch2/benchmark/detail/catch_timing.hpp>
#include <catch2/internal/catch_meta.hpp>
#include <catch2/internal/catch_move_and_forward.hpp>
#include <type_traits>
namespace Catch {
namespace Benchmark {
namespace Detail {
template <typename Clock, typename Fun>
TimingOf<Clock, Fun, int> measure_one(Fun&& fun, int iters, std::false_type) {
return Detail::measure<Clock>(fun, iters);
}
template <typename Clock, typename Fun>
TimingOf<Clock, Fun, Chronometer> measure_one(Fun&& fun, int iters, std::true_type) {
Detail::ChronometerModel<Clock> meter;
auto&& result = Detail::complete_invoke(fun, Chronometer(meter, iters));
return { meter.elapsed(), CATCH_MOVE(result), iters };
}
template <typename Clock, typename Fun>
using run_for_at_least_argument_t = std::conditional_t<is_callable<Fun(Chronometer)>::value, Chronometer, int>;
[[noreturn]]
void throw_optimized_away_error();
template <typename Clock, typename Fun>
TimingOf<Clock, Fun, run_for_at_least_argument_t<Clock, Fun>>
run_for_at_least(ClockDuration<Clock> how_long,
const int initial_iterations,
Fun&& fun) {
auto iters = initial_iterations;
while (iters < (1 << 30)) {
auto&& Timing = measure_one<Clock>(fun, iters, is_callable<Fun(Chronometer)>());
if (Timing.elapsed >= how_long) {
return { Timing.elapsed, CATCH_MOVE(Timing.result), iters };
}
iters *= 2;
}
throw_optimized_away_error();
}
} // namespace Detail
} // namespace Benchmark
} // namespace Catch
#endif // CATCH_RUN_FOR_AT_LEAST_HPP_INCLUDED

View File

@ -0,0 +1,330 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
// Adapted from donated nonius code.
#include <catch2/benchmark/detail/catch_stats.hpp>
#include <catch2/internal/catch_compiler_capabilities.hpp>
#include <cassert>
#include <cstddef>
#include <numeric>
#include <random>
#if defined(CATCH_CONFIG_USE_ASYNC)
#include <future>
#endif
namespace Catch {
namespace Benchmark {
namespace Detail {
namespace {
template <typename URng, typename Estimator>
static sample
resample( URng& rng,
unsigned int resamples,
std::vector<double>::const_iterator first,
std::vector<double>::const_iterator last,
Estimator& estimator ) {
auto n = static_cast<size_t>( last - first );
std::uniform_int_distribution<decltype( n )> dist( 0,
n - 1 );
sample out;
out.reserve( resamples );
// We allocate the vector outside the loop to avoid realloc
// per resample
std::vector<double> resampled;
resampled.reserve( n );
for ( size_t i = 0; i < resamples; ++i ) {
resampled.clear();
for ( size_t s = 0; s < n; ++s ) {
resampled.push_back(
first[static_cast<std::ptrdiff_t>(
dist( rng ) )] );
}
const auto estimate =
estimator( resampled.begin(), resampled.end() );
out.push_back( estimate );
}
std::sort( out.begin(), out.end() );
return out;
}
static double outlier_variance( Estimate<double> mean,
Estimate<double> stddev,
int n ) {
double sb = stddev.point;
double mn = mean.point / n;
double mg_min = mn / 2.;
double sg = (std::min)( mg_min / 4., sb / std::sqrt( n ) );
double sg2 = sg * sg;
double sb2 = sb * sb;
auto c_max = [n, mn, sb2, sg2]( double x ) -> double {
double k = mn - x;
double d = k * k;
double nd = n * d;
double k0 = -n * nd;
double k1 = sb2 - n * sg2 + nd;
double det = k1 * k1 - 4 * sg2 * k0;
return static_cast<int>( -2. * k0 /
( k1 + std::sqrt( det ) ) );
};
auto var_out = [n, sb2, sg2]( double c ) {
double nc = n - c;
return ( nc / n ) * ( sb2 - nc * sg2 );
};
return (std::min)( var_out( 1 ),
var_out(
(std::min)( c_max( 0. ),
c_max( mg_min ) ) ) ) /
sb2;
}
static double erf_inv( double x ) {
// Code accompanying the article "Approximating the erfinv
// function" in GPU Computing Gems, Volume 2
double w, p;
w = -log( ( 1.0 - x ) * ( 1.0 + x ) );
if ( w < 6.250000 ) {
w = w - 3.125000;
p = -3.6444120640178196996e-21;
p = -1.685059138182016589e-19 + p * w;
p = 1.2858480715256400167e-18 + p * w;
p = 1.115787767802518096e-17 + p * w;
p = -1.333171662854620906e-16 + p * w;
p = 2.0972767875968561637e-17 + p * w;
p = 6.6376381343583238325e-15 + p * w;
p = -4.0545662729752068639e-14 + p * w;
p = -8.1519341976054721522e-14 + p * w;
p = 2.6335093153082322977e-12 + p * w;
p = -1.2975133253453532498e-11 + p * w;
p = -5.4154120542946279317e-11 + p * w;
p = 1.051212273321532285e-09 + p * w;
p = -4.1126339803469836976e-09 + p * w;
p = -2.9070369957882005086e-08 + p * w;
p = 4.2347877827932403518e-07 + p * w;
p = -1.3654692000834678645e-06 + p * w;
p = -1.3882523362786468719e-05 + p * w;
p = 0.0001867342080340571352 + p * w;
p = -0.00074070253416626697512 + p * w;
p = -0.0060336708714301490533 + p * w;
p = 0.24015818242558961693 + p * w;
p = 1.6536545626831027356 + p * w;
} else if ( w < 16.000000 ) {
w = sqrt( w ) - 3.250000;
p = 2.2137376921775787049e-09;
p = 9.0756561938885390979e-08 + p * w;
p = -2.7517406297064545428e-07 + p * w;
p = 1.8239629214389227755e-08 + p * w;
p = 1.5027403968909827627e-06 + p * w;
p = -4.013867526981545969e-06 + p * w;
p = 2.9234449089955446044e-06 + p * w;
p = 1.2475304481671778723e-05 + p * w;
p = -4.7318229009055733981e-05 + p * w;
p = 6.8284851459573175448e-05 + p * w;
p = 2.4031110387097893999e-05 + p * w;
p = -0.0003550375203628474796 + p * w;
p = 0.00095328937973738049703 + p * w;
p = -0.0016882755560235047313 + p * w;
p = 0.0024914420961078508066 + p * w;
p = -0.0037512085075692412107 + p * w;
p = 0.005370914553590063617 + p * w;
p = 1.0052589676941592334 + p * w;
p = 3.0838856104922207635 + p * w;
} else {
w = sqrt( w ) - 5.000000;
p = -2.7109920616438573243e-11;
p = -2.5556418169965252055e-10 + p * w;
p = 1.5076572693500548083e-09 + p * w;
p = -3.7894654401267369937e-09 + p * w;
p = 7.6157012080783393804e-09 + p * w;
p = -1.4960026627149240478e-08 + p * w;
p = 2.9147953450901080826e-08 + p * w;
p = -6.7711997758452339498e-08 + p * w;
p = 2.2900482228026654717e-07 + p * w;
p = -9.9298272942317002539e-07 + p * w;
p = 4.5260625972231537039e-06 + p * w;
p = -1.9681778105531670567e-05 + p * w;
p = 7.5995277030017761139e-05 + p * w;
p = -0.00021503011930044477347 + p * w;
p = -0.00013871931833623122026 + p * w;
p = 1.0103004648645343977 + p * w;
p = 4.8499064014085844221 + p * w;
}
return p * x;
}
static double
standard_deviation( std::vector<double>::const_iterator first,
std::vector<double>::const_iterator last ) {
auto m = Catch::Benchmark::Detail::mean( first, last );
double variance =
std::accumulate( first,
last,
0.,
[m]( double a, double b ) {
double diff = b - m;
return a + diff * diff;
} ) /
( last - first );
return std::sqrt( variance );
}
} // namespace
} // namespace Detail
} // namespace Benchmark
} // namespace Catch
namespace Catch {
namespace Benchmark {
namespace Detail {
#if defined( __GNUC__ ) || defined( __clang__ )
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wfloat-equal"
#endif
bool directCompare( double lhs, double rhs ) { return lhs == rhs; }
#if defined( __GNUC__ ) || defined( __clang__ )
# pragma GCC diagnostic pop
#endif
double weighted_average_quantile(int k, int q, std::vector<double>::iterator first, std::vector<double>::iterator last) {
auto count = last - first;
double idx = (count - 1) * k / static_cast<double>(q);
int j = static_cast<int>(idx);
double g = idx - j;
std::nth_element(first, first + j, last);
auto xj = first[j];
if ( directCompare( g, 0 ) ) {
return xj;
}
auto xj1 = *std::min_element(first + (j + 1), last);
return xj + g * (xj1 - xj);
}
OutlierClassification
classify_outliers( std::vector<double>::const_iterator first,
std::vector<double>::const_iterator last ) {
std::vector<double> copy( first, last );
auto q1 = weighted_average_quantile( 1, 4, copy.begin(), copy.end() );
auto q3 = weighted_average_quantile( 3, 4, copy.begin(), copy.end() );
auto iqr = q3 - q1;
auto los = q1 - ( iqr * 3. );
auto lom = q1 - ( iqr * 1.5 );
auto him = q3 + ( iqr * 1.5 );
auto his = q3 + ( iqr * 3. );
OutlierClassification o;
for ( ; first != last; ++first ) {
const double t = *first;
if ( t < los ) {
++o.low_severe;
} else if ( t < lom ) {
++o.low_mild;
} else if ( t > his ) {
++o.high_severe;
} else if ( t > him ) {
++o.high_mild;
}
++o.samples_seen;
}
return o;
}
double mean( std::vector<double>::const_iterator first,
std::vector<double>::const_iterator last ) {
auto count = last - first;
double sum = 0.;
while (first != last) {
sum += *first;
++first;
}
return sum / static_cast<double>(count);
}
double erfc_inv(double x) {
return erf_inv(1.0 - x);
}
double normal_quantile(double p) {
static const double ROOT_TWO = std::sqrt(2.0);
double result = 0.0;
assert(p >= 0 && p <= 1);
if (p < 0 || p > 1) {
return result;
}
result = -erfc_inv(2.0 * p);
// result *= normal distribution standard deviation (1.0) * sqrt(2)
result *= /*sd * */ ROOT_TWO;
// result += normal disttribution mean (0)
return result;
}
bootstrap_analysis analyse_samples(double confidence_level,
unsigned int n_resamples,
std::vector<double>::iterator first,
std::vector<double>::iterator last) {
CATCH_INTERNAL_START_WARNINGS_SUPPRESSION
CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS
static std::random_device entropy;
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION
auto n = static_cast<int>(last - first); // seriously, one can't use integral types without hell in C++
auto mean = &Detail::mean;
auto stddev = &standard_deviation;
#if defined(CATCH_CONFIG_USE_ASYNC)
auto Estimate = [=](double(*f)(std::vector<double>::const_iterator,
std::vector<double>::const_iterator)) {
auto seed = entropy();
return std::async(std::launch::async, [=] {
std::mt19937 rng(seed);
auto resampled = resample(rng, n_resamples, first, last, f);
return bootstrap(confidence_level, first, last, resampled, f);
});
};
auto mean_future = Estimate(mean);
auto stddev_future = Estimate(stddev);
auto mean_estimate = mean_future.get();
auto stddev_estimate = stddev_future.get();
#else
auto Estimate = [=](double(*f)(std::vector<double>::const_iterator,
std::vector<double>::const_iterator)) {
auto seed = entropy();
std::mt19937 rng(seed);
auto resampled = resample(rng, n_resamples, first, last, f);
return bootstrap(confidence_level, first, last, resampled, f);
};
auto mean_estimate = Estimate(mean);
auto stddev_estimate = Estimate(stddev);
#endif // CATCH_USE_ASYNC
double outlier_variance = Detail::outlier_variance(mean_estimate, stddev_estimate, n);
return { mean_estimate, stddev_estimate, outlier_variance };
}
} // namespace Detail
} // namespace Benchmark
} // namespace Catch

View File

@ -0,0 +1,125 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
// Adapted from donated nonius code.
#ifndef CATCH_STATS_HPP_INCLUDED
#define CATCH_STATS_HPP_INCLUDED
#include <catch2/benchmark/catch_estimate.hpp>
#include <catch2/benchmark/catch_outlier_classification.hpp>
#include <algorithm>
#include <vector>
#include <cmath>
namespace Catch {
namespace Benchmark {
namespace Detail {
using sample = std::vector<double>;
// Used when we know we want == comparison of two doubles
// to centralize warning suppression
bool directCompare( double lhs, double rhs );
double weighted_average_quantile(int k, int q, std::vector<double>::iterator first, std::vector<double>::iterator last);
OutlierClassification
classify_outliers( std::vector<double>::const_iterator first,
std::vector<double>::const_iterator last );
double mean( std::vector<double>::const_iterator first,
std::vector<double>::const_iterator last );
template <typename Estimator>
sample jackknife(Estimator&& estimator,
std::vector<double>::iterator first,
std::vector<double>::iterator last) {
auto n = static_cast<size_t>(last - first);
auto second = first;
++second;
sample results;
results.reserve(n);
for (auto it = first; it != last; ++it) {
std::iter_swap(it, first);
results.push_back(estimator(second, last));
}
return results;
}
inline double normal_cdf(double x) {
return std::erfc(-x / std::sqrt(2.0)) / 2.0;
}
double erfc_inv(double x);
double normal_quantile(double p);
template <typename Estimator>
Estimate<double> bootstrap( double confidence_level,
std::vector<double>::iterator first,
std::vector<double>::iterator last,
sample const& resample,
Estimator&& estimator ) {
auto n_samples = last - first;
double point = estimator(first, last);
// Degenerate case with a single sample
if (n_samples == 1) return { point, point, point, confidence_level };
sample jack = jackknife(estimator, first, last);
double jack_mean = mean(jack.begin(), jack.end());
double sum_squares = 0, sum_cubes = 0;
for (double x : jack) {
auto difference = jack_mean - x;
auto square = difference * difference;
auto cube = square * difference;
sum_squares += square; sum_cubes += cube;
}
double accel = sum_cubes / (6 * std::pow(sum_squares, 1.5));
long n = static_cast<long>(resample.size());
double prob_n = std::count_if(resample.begin(), resample.end(), [point](double x) { return x < point; }) / static_cast<double>(n);
// degenerate case with uniform samples
if ( directCompare( prob_n, 0. ) ) {
return { point, point, point, confidence_level };
}
double bias = normal_quantile(prob_n);
double z1 = normal_quantile((1. - confidence_level) / 2.);
auto cumn = [n]( double x ) -> long {
return std::lround( normal_cdf( x ) * static_cast<double>(n) );
};
auto a = [bias, accel](double b) { return bias + b / (1. - accel * b); };
double b1 = bias + z1;
double b2 = bias - z1;
double a1 = a(b1);
double a2 = a(b2);
auto lo = static_cast<size_t>((std::max)(cumn(a1), 0l));
auto hi = static_cast<size_t>((std::min)(cumn(a2), n - 1));
return { point, resample[lo], resample[hi], confidence_level };
}
struct bootstrap_analysis {
Estimate<double> mean;
Estimate<double> standard_deviation;
double outlier_variance;
};
bootstrap_analysis analyse_samples(double confidence_level,
unsigned int n_resamples,
std::vector<double>::iterator first,
std::vector<double>::iterator last);
} // namespace Detail
} // namespace Benchmark
} // namespace Catch
#endif // CATCH_STATS_HPP_INCLUDED

View File

@ -0,0 +1,31 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
// Adapted from donated nonius code.
#ifndef CATCH_TIMING_HPP_INCLUDED
#define CATCH_TIMING_HPP_INCLUDED
#include <catch2/benchmark/catch_clock.hpp>
#include <catch2/benchmark/detail/catch_complete_invoke.hpp>
#include <type_traits>
namespace Catch {
namespace Benchmark {
template <typename Duration, typename Result>
struct Timing {
Duration elapsed;
Result result;
int iterations;
};
template <typename Clock, typename Func, typename... Args>
using TimingOf = Timing<ClockDuration<Clock>, Detail::CompleteType_t<FunctionReturnType<Func, Args...>>>;
} // namespace Benchmark
} // namespace Catch
#endif // CATCH_TIMING_HPP_INCLUDED

View File

@ -0,0 +1,129 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
/** \file
* This is a convenience header for Catch2. It includes **all** of Catch2 headers.
*
* Generally the Catch2 users should use specific includes they need,
* but this header can be used instead for ease-of-experimentation, or
* just plain convenience, at the cost of (significantly) increased
* compilation times.
*
* When a new header is added to either the top level folder, or to the
* corresponding internal subfolder, it should be added here. Headers
* added to the various subparts (e.g. matchers, generators, etc...),
* should go their respective catch-all headers.
*/
#ifndef CATCH_ALL_HPP_INCLUDED
#define CATCH_ALL_HPP_INCLUDED
#include <catch2/benchmark/catch_benchmark_all.hpp>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_assertion_info.hpp>
#include <catch2/catch_assertion_result.hpp>
#include <catch2/catch_config.hpp>
#include <catch2/catch_get_random_seed.hpp>
#include <catch2/catch_message.hpp>
#include <catch2/catch_section_info.hpp>
#include <catch2/catch_session.hpp>
#include <catch2/catch_tag_alias.hpp>
#include <catch2/catch_tag_alias_autoregistrar.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include <catch2/catch_test_case_info.hpp>
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_test_spec.hpp>
#include <catch2/catch_timer.hpp>
#include <catch2/catch_tostring.hpp>
#include <catch2/catch_totals.hpp>
#include <catch2/catch_translate_exception.hpp>
#include <catch2/catch_version.hpp>
#include <catch2/catch_version_macros.hpp>
#include <catch2/generators/catch_generators_all.hpp>
#include <catch2/interfaces/catch_interfaces_all.hpp>
#include <catch2/internal/catch_assertion_handler.hpp>
#include <catch2/internal/catch_case_insensitive_comparisons.hpp>
#include <catch2/internal/catch_case_sensitive.hpp>
#include <catch2/internal/catch_clara.hpp>
#include <catch2/internal/catch_commandline.hpp>
#include <catch2/internal/catch_compare_traits.hpp>
#include <catch2/internal/catch_compiler_capabilities.hpp>
#include <catch2/internal/catch_config_android_logwrite.hpp>
#include <catch2/internal/catch_config_counter.hpp>
#include <catch2/internal/catch_config_static_analysis_support.hpp>
#include <catch2/internal/catch_config_uncaught_exceptions.hpp>
#include <catch2/internal/catch_config_wchar.hpp>
#include <catch2/internal/catch_console_colour.hpp>
#include <catch2/internal/catch_console_width.hpp>
#include <catch2/internal/catch_container_nonmembers.hpp>
#include <catch2/internal/catch_context.hpp>
#include <catch2/internal/catch_debug_console.hpp>
#include <catch2/internal/catch_debugger.hpp>
#include <catch2/internal/catch_decomposer.hpp>
#include <catch2/internal/catch_enforce.hpp>
#include <catch2/internal/catch_enum_values_registry.hpp>
#include <catch2/internal/catch_errno_guard.hpp>
#include <catch2/internal/catch_exception_translator_registry.hpp>
#include <catch2/internal/catch_fatal_condition_handler.hpp>
#include <catch2/internal/catch_floating_point_helpers.hpp>
#include <catch2/internal/catch_getenv.hpp>
#include <catch2/internal/catch_is_permutation.hpp>
#include <catch2/internal/catch_istream.hpp>
#include <catch2/internal/catch_lazy_expr.hpp>
#include <catch2/internal/catch_leak_detector.hpp>
#include <catch2/internal/catch_list.hpp>
#include <catch2/internal/catch_logical_traits.hpp>
#include <catch2/internal/catch_message_info.hpp>
#include <catch2/internal/catch_meta.hpp>
#include <catch2/internal/catch_move_and_forward.hpp>
#include <catch2/internal/catch_noncopyable.hpp>
#include <catch2/internal/catch_optional.hpp>
#include <catch2/internal/catch_output_redirect.hpp>
#include <catch2/internal/catch_parse_numbers.hpp>
#include <catch2/internal/catch_platform.hpp>
#include <catch2/internal/catch_polyfills.hpp>
#include <catch2/internal/catch_preprocessor.hpp>
#include <catch2/internal/catch_preprocessor_internal_stringify.hpp>
#include <catch2/internal/catch_preprocessor_remove_parens.hpp>
#include <catch2/internal/catch_random_number_generator.hpp>
#include <catch2/internal/catch_random_seed_generation.hpp>
#include <catch2/internal/catch_reporter_registry.hpp>
#include <catch2/internal/catch_reporter_spec_parser.hpp>
#include <catch2/internal/catch_result_type.hpp>
#include <catch2/internal/catch_reusable_string_stream.hpp>
#include <catch2/internal/catch_run_context.hpp>
#include <catch2/internal/catch_section.hpp>
#include <catch2/internal/catch_sharding.hpp>
#include <catch2/internal/catch_singletons.hpp>
#include <catch2/internal/catch_source_line_info.hpp>
#include <catch2/internal/catch_startup_exception_registry.hpp>
#include <catch2/internal/catch_stdstreams.hpp>
#include <catch2/internal/catch_stream_end_stop.hpp>
#include <catch2/internal/catch_string_manip.hpp>
#include <catch2/internal/catch_stringref.hpp>
#include <catch2/internal/catch_tag_alias_registry.hpp>
#include <catch2/internal/catch_template_test_registry.hpp>
#include <catch2/internal/catch_test_case_info_hasher.hpp>
#include <catch2/internal/catch_test_case_registry_impl.hpp>
#include <catch2/internal/catch_test_case_tracker.hpp>
#include <catch2/internal/catch_test_failure_exception.hpp>
#include <catch2/internal/catch_test_macro_impl.hpp>
#include <catch2/internal/catch_test_registry.hpp>
#include <catch2/internal/catch_test_run_info.hpp>
#include <catch2/internal/catch_test_spec_parser.hpp>
#include <catch2/internal/catch_textflow.hpp>
#include <catch2/internal/catch_to_string.hpp>
#include <catch2/internal/catch_uncaught_exceptions.hpp>
#include <catch2/internal/catch_unique_name.hpp>
#include <catch2/internal/catch_unique_ptr.hpp>
#include <catch2/internal/catch_void_type.hpp>
#include <catch2/internal/catch_wildcard_pattern.hpp>
#include <catch2/internal/catch_xmlwriter.hpp>
#include <catch2/matchers/catch_matchers_all.hpp>
#include <catch2/reporters/catch_reporters_all.hpp>
#endif // CATCH_ALL_HPP_INCLUDED

View File

@ -0,0 +1,85 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/catch_approx.hpp>
#include <catch2/internal/catch_enforce.hpp>
#include <catch2/internal/catch_reusable_string_stream.hpp>
#include <cmath>
#include <limits>
namespace {
// Performs equivalent check of std::fabs(lhs - rhs) <= margin
// But without the subtraction to allow for INFINITY in comparison
bool marginComparison(double lhs, double rhs, double margin) {
return (lhs + margin >= rhs) && (rhs + margin >= lhs);
}
}
namespace Catch {
Approx::Approx ( double value )
: m_epsilon( std::numeric_limits<float>::epsilon()*100. ),
m_margin( 0.0 ),
m_scale( 0.0 ),
m_value( value )
{}
Approx Approx::custom() {
return Approx( 0 );
}
Approx Approx::operator-() const {
auto temp(*this);
temp.m_value = -temp.m_value;
return temp;
}
std::string Approx::toString() const {
ReusableStringStream rss;
rss << "Approx( " << ::Catch::Detail::stringify( m_value ) << " )";
return rss.str();
}
bool Approx::equalityComparisonImpl(const double other) const {
// First try with fixed margin, then compute margin based on epsilon, scale and Approx's value
// Thanks to Richard Harris for his help refining the scaled margin value
return marginComparison(m_value, other, m_margin)
|| marginComparison(m_value, other, m_epsilon * (m_scale + std::fabs(std::isinf(m_value)? 0 : m_value)));
}
void Approx::setMargin(double newMargin) {
CATCH_ENFORCE(newMargin >= 0,
"Invalid Approx::margin: " << newMargin << '.'
<< " Approx::Margin has to be non-negative.");
m_margin = newMargin;
}
void Approx::setEpsilon(double newEpsilon) {
CATCH_ENFORCE(newEpsilon >= 0 && newEpsilon <= 1.0,
"Invalid Approx::epsilon: " << newEpsilon << '.'
<< " Approx::epsilon has to be in [0, 1]");
m_epsilon = newEpsilon;
}
namespace literals {
Approx operator "" _a(long double val) {
return Approx(val);
}
Approx operator "" _a(unsigned long long val) {
return Approx(val);
}
} // end namespace literals
std::string StringMaker<Catch::Approx>::convert(Catch::Approx const& value) {
return value.toString();
}
} // end namespace Catch

View File

@ -0,0 +1,128 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#ifndef CATCH_APPROX_HPP_INCLUDED
#define CATCH_APPROX_HPP_INCLUDED
#include <catch2/catch_tostring.hpp>
#include <type_traits>
namespace Catch {
class Approx {
private:
bool equalityComparisonImpl(double other) const;
// Sets and validates the new margin (margin >= 0)
void setMargin(double margin);
// Sets and validates the new epsilon (0 < epsilon < 1)
void setEpsilon(double epsilon);
public:
explicit Approx ( double value );
static Approx custom();
Approx operator-() const;
template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>
Approx operator()( T const& value ) const {
Approx approx( static_cast<double>(value) );
approx.m_epsilon = m_epsilon;
approx.m_margin = m_margin;
approx.m_scale = m_scale;
return approx;
}
template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>
explicit Approx( T const& value ): Approx(static_cast<double>(value))
{}
template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>
friend bool operator == ( const T& lhs, Approx const& rhs ) {
auto lhs_v = static_cast<double>(lhs);
return rhs.equalityComparisonImpl(lhs_v);
}
template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>
friend bool operator == ( Approx const& lhs, const T& rhs ) {
return operator==( rhs, lhs );
}
template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>
friend bool operator != ( T const& lhs, Approx const& rhs ) {
return !operator==( lhs, rhs );
}
template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>
friend bool operator != ( Approx const& lhs, T const& rhs ) {
return !operator==( rhs, lhs );
}
template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>
friend bool operator <= ( T const& lhs, Approx const& rhs ) {
return static_cast<double>(lhs) < rhs.m_value || lhs == rhs;
}
template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>
friend bool operator <= ( Approx const& lhs, T const& rhs ) {
return lhs.m_value < static_cast<double>(rhs) || lhs == rhs;
}
template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>
friend bool operator >= ( T const& lhs, Approx const& rhs ) {
return static_cast<double>(lhs) > rhs.m_value || lhs == rhs;
}
template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>
friend bool operator >= ( Approx const& lhs, T const& rhs ) {
return lhs.m_value > static_cast<double>(rhs) || lhs == rhs;
}
template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>
Approx& epsilon( T const& newEpsilon ) {
const auto epsilonAsDouble = static_cast<double>(newEpsilon);
setEpsilon(epsilonAsDouble);
return *this;
}
template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>
Approx& margin( T const& newMargin ) {
const auto marginAsDouble = static_cast<double>(newMargin);
setMargin(marginAsDouble);
return *this;
}
template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>
Approx& scale( T const& newScale ) {
m_scale = static_cast<double>(newScale);
return *this;
}
std::string toString() const;
private:
double m_epsilon;
double m_margin;
double m_scale;
double m_value;
};
namespace literals {
Approx operator ""_a(long double val);
Approx operator ""_a(unsigned long long val);
} // end namespace literals
template<>
struct StringMaker<Catch::Approx> {
static std::string convert(Catch::Approx const& value);
};
} // end namespace Catch
#endif // CATCH_APPROX_HPP_INCLUDED

View File

@ -0,0 +1,28 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#ifndef CATCH_ASSERTION_INFO_HPP_INCLUDED
#define CATCH_ASSERTION_INFO_HPP_INCLUDED
#include <catch2/internal/catch_result_type.hpp>
#include <catch2/internal/catch_source_line_info.hpp>
#include <catch2/internal/catch_stringref.hpp>
namespace Catch {
struct AssertionInfo {
// AssertionInfo() = delete;
StringRef macroName;
SourceLineInfo lineInfo;
StringRef capturedExpression;
ResultDisposition::Flags resultDisposition;
};
} // end namespace Catch
#endif // CATCH_ASSERTION_INFO_HPP_INCLUDED

View File

@ -0,0 +1,105 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/catch_assertion_result.hpp>
#include <catch2/internal/catch_reusable_string_stream.hpp>
#include <catch2/internal/catch_move_and_forward.hpp>
namespace Catch {
AssertionResultData::AssertionResultData(ResultWas::OfType _resultType, LazyExpression const & _lazyExpression):
lazyExpression(_lazyExpression),
resultType(_resultType) {}
std::string AssertionResultData::reconstructExpression() const {
if( reconstructedExpression.empty() ) {
if( lazyExpression ) {
ReusableStringStream rss;
rss << lazyExpression;
reconstructedExpression = rss.str();
}
}
return reconstructedExpression;
}
AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData&& data )
: m_info( info ),
m_resultData( CATCH_MOVE(data) )
{}
// Result was a success
bool AssertionResult::succeeded() const {
return Catch::isOk( m_resultData.resultType );
}
// Result was a success, or failure is suppressed
bool AssertionResult::isOk() const {
return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition );
}
ResultWas::OfType AssertionResult::getResultType() const {
return m_resultData.resultType;
}
bool AssertionResult::hasExpression() const {
return !m_info.capturedExpression.empty();
}
bool AssertionResult::hasMessage() const {
return !m_resultData.message.empty();
}
std::string AssertionResult::getExpression() const {
// Possibly overallocating by 3 characters should be basically free
std::string expr; expr.reserve(m_info.capturedExpression.size() + 3);
if (isFalseTest(m_info.resultDisposition)) {
expr += "!(";
}
expr += m_info.capturedExpression;
if (isFalseTest(m_info.resultDisposition)) {
expr += ')';
}
return expr;
}
std::string AssertionResult::getExpressionInMacro() const {
if ( m_info.macroName.empty() ) {
return static_cast<std::string>( m_info.capturedExpression );
}
std::string expr;
expr.reserve( m_info.macroName.size() + m_info.capturedExpression.size() + 4 );
expr += m_info.macroName;
expr += "( ";
expr += m_info.capturedExpression;
expr += " )";
return expr;
}
bool AssertionResult::hasExpandedExpression() const {
return hasExpression() && getExpandedExpression() != getExpression();
}
std::string AssertionResult::getExpandedExpression() const {
std::string expr = m_resultData.reconstructExpression();
return expr.empty()
? getExpression()
: expr;
}
StringRef AssertionResult::getMessage() const {
return m_resultData.message;
}
SourceLineInfo AssertionResult::getSourceInfo() const {
return m_info.lineInfo;
}
StringRef AssertionResult::getTestMacroName() const {
return m_info.macroName;
}
} // end namespace Catch

View File

@ -0,0 +1,60 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#ifndef CATCH_ASSERTION_RESULT_HPP_INCLUDED
#define CATCH_ASSERTION_RESULT_HPP_INCLUDED
#include <catch2/catch_assertion_info.hpp>
#include <catch2/internal/catch_result_type.hpp>
#include <catch2/internal/catch_source_line_info.hpp>
#include <catch2/internal/catch_stringref.hpp>
#include <catch2/internal/catch_lazy_expr.hpp>
#include <string>
namespace Catch {
struct AssertionResultData
{
AssertionResultData() = delete;
AssertionResultData( ResultWas::OfType _resultType, LazyExpression const& _lazyExpression );
std::string message;
mutable std::string reconstructedExpression;
LazyExpression lazyExpression;
ResultWas::OfType resultType;
std::string reconstructExpression() const;
};
class AssertionResult {
public:
AssertionResult() = delete;
AssertionResult( AssertionInfo const& info, AssertionResultData&& data );
bool isOk() const;
bool succeeded() const;
ResultWas::OfType getResultType() const;
bool hasExpression() const;
bool hasMessage() const;
std::string getExpression() const;
std::string getExpressionInMacro() const;
bool hasExpandedExpression() const;
std::string getExpandedExpression() const;
StringRef getMessage() const;
SourceLineInfo getSourceInfo() const;
StringRef getTestMacroName() const;
//protected:
AssertionInfo m_info;
AssertionResultData m_resultData;
};
} // end namespace Catch
#endif // CATCH_ASSERTION_RESULT_HPP_INCLUDED

View File

@ -0,0 +1,247 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/catch_config.hpp>
#include <catch2/catch_user_config.hpp>
#include <catch2/internal/catch_enforce.hpp>
#include <catch2/internal/catch_parse_numbers.hpp>
#include <catch2/internal/catch_stdstreams.hpp>
#include <catch2/internal/catch_stringref.hpp>
#include <catch2/internal/catch_string_manip.hpp>
#include <catch2/internal/catch_test_spec_parser.hpp>
#include <catch2/interfaces/catch_interfaces_tag_alias_registry.hpp>
#include <catch2/internal/catch_getenv.hpp>
#include <fstream>
namespace Catch {
namespace {
static bool enableBazelEnvSupport() {
#if defined( CATCH_CONFIG_BAZEL_SUPPORT )
return true;
#else
return Detail::getEnv( "BAZEL_TEST" ) != nullptr;
#endif
}
struct bazelShardingOptions {
unsigned int shardIndex, shardCount;
std::string shardFilePath;
};
static Optional<bazelShardingOptions> readBazelShardingOptions() {
const auto bazelShardIndex = Detail::getEnv( "TEST_SHARD_INDEX" );
const auto bazelShardTotal = Detail::getEnv( "TEST_TOTAL_SHARDS" );
const auto bazelShardInfoFile = Detail::getEnv( "TEST_SHARD_STATUS_FILE" );
const bool has_all =
bazelShardIndex && bazelShardTotal && bazelShardInfoFile;
if ( !has_all ) {
// We provide nice warning message if the input is
// misconfigured.
auto warn = []( const char* env_var ) {
Catch::cerr()
<< "Warning: Bazel shard configuration is missing '"
<< env_var << "'. Shard configuration is skipped.\n";
};
if ( !bazelShardIndex ) {
warn( "TEST_SHARD_INDEX" );
}
if ( !bazelShardTotal ) {
warn( "TEST_TOTAL_SHARDS" );
}
if ( !bazelShardInfoFile ) {
warn( "TEST_SHARD_STATUS_FILE" );
}
return {};
}
auto shardIndex = parseUInt( bazelShardIndex );
if ( !shardIndex ) {
Catch::cerr()
<< "Warning: could not parse 'TEST_SHARD_INDEX' ('" << bazelShardIndex
<< "') as unsigned int.\n";
return {};
}
auto shardTotal = parseUInt( bazelShardTotal );
if ( !shardTotal ) {
Catch::cerr()
<< "Warning: could not parse 'TEST_TOTAL_SHARD' ('"
<< bazelShardTotal << "') as unsigned int.\n";
return {};
}
return bazelShardingOptions{
*shardIndex, *shardTotal, bazelShardInfoFile };
}
} // end namespace
bool operator==( ProcessedReporterSpec const& lhs,
ProcessedReporterSpec const& rhs ) {
return lhs.name == rhs.name &&
lhs.outputFilename == rhs.outputFilename &&
lhs.colourMode == rhs.colourMode &&
lhs.customOptions == rhs.customOptions;
}
Config::Config( ConfigData const& data ):
m_data( data ) {
// We need to trim filter specs to avoid trouble with superfluous
// whitespace (esp. important for bdd macros, as those are manually
// aligned with whitespace).
for (auto& elem : m_data.testsOrTags) {
elem = trim(elem);
}
for (auto& elem : m_data.sectionsToRun) {
elem = trim(elem);
}
// Insert the default reporter if user hasn't asked for a specific one
if ( m_data.reporterSpecifications.empty() ) {
m_data.reporterSpecifications.push_back( {
#if defined( CATCH_CONFIG_DEFAULT_REPORTER )
CATCH_CONFIG_DEFAULT_REPORTER,
#else
"console",
#endif
{}, {}, {}
} );
}
if ( enableBazelEnvSupport() ) {
readBazelEnvVars();
}
// Bazel support can modify the test specs, so parsing has to happen
// after reading Bazel env vars.
TestSpecParser parser( ITagAliasRegistry::get() );
if ( !m_data.testsOrTags.empty() ) {
m_hasTestFilters = true;
for ( auto const& testOrTags : m_data.testsOrTags ) {
parser.parse( testOrTags );
}
}
m_testSpec = parser.testSpec();
// We now fixup the reporter specs to handle default output spec,
// default colour spec, etc
bool defaultOutputUsed = false;
for ( auto const& reporterSpec : m_data.reporterSpecifications ) {
// We do the default-output check separately, while always
// using the default output below to make the code simpler
// and avoid superfluous copies.
if ( reporterSpec.outputFile().none() ) {
CATCH_ENFORCE( !defaultOutputUsed,
"Internal error: cannot use default output for "
"multiple reporters" );
defaultOutputUsed = true;
}
m_processedReporterSpecs.push_back( ProcessedReporterSpec{
reporterSpec.name(),
reporterSpec.outputFile() ? *reporterSpec.outputFile()
: data.defaultOutputFilename,
reporterSpec.colourMode().valueOr( data.defaultColourMode ),
reporterSpec.customOptions() } );
}
}
Config::~Config() = default;
bool Config::listTests() const { return m_data.listTests; }
bool Config::listTags() const { return m_data.listTags; }
bool Config::listReporters() const { return m_data.listReporters; }
bool Config::listListeners() const { return m_data.listListeners; }
std::vector<std::string> const& Config::getTestsOrTags() const { return m_data.testsOrTags; }
std::vector<std::string> const& Config::getSectionsToRun() const { return m_data.sectionsToRun; }
std::vector<ReporterSpec> const& Config::getReporterSpecs() const {
return m_data.reporterSpecifications;
}
std::vector<ProcessedReporterSpec> const&
Config::getProcessedReporterSpecs() const {
return m_processedReporterSpecs;
}
TestSpec const& Config::testSpec() const { return m_testSpec; }
bool Config::hasTestFilters() const { return m_hasTestFilters; }
bool Config::showHelp() const { return m_data.showHelp; }
// IConfig interface
bool Config::allowThrows() const { return !m_data.noThrow; }
StringRef Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; }
bool Config::includeSuccessfulResults() const { return m_data.showSuccessfulTests; }
bool Config::warnAboutMissingAssertions() const {
return !!( m_data.warnings & WarnAbout::NoAssertions );
}
bool Config::warnAboutUnmatchedTestSpecs() const {
return !!( m_data.warnings & WarnAbout::UnmatchedTestSpec );
}
bool Config::zeroTestsCountAsSuccess() const { return m_data.allowZeroTests; }
ShowDurations Config::showDurations() const { return m_data.showDurations; }
double Config::minDuration() const { return m_data.minDuration; }
TestRunOrder Config::runOrder() const { return m_data.runOrder; }
uint32_t Config::rngSeed() const { return m_data.rngSeed; }
unsigned int Config::shardCount() const { return m_data.shardCount; }
unsigned int Config::shardIndex() const { return m_data.shardIndex; }
ColourMode Config::defaultColourMode() const { return m_data.defaultColourMode; }
bool Config::shouldDebugBreak() const { return m_data.shouldDebugBreak; }
int Config::abortAfter() const { return m_data.abortAfter; }
bool Config::showInvisibles() const { return m_data.showInvisibles; }
Verbosity Config::verbosity() const { return m_data.verbosity; }
bool Config::skipBenchmarks() const { return m_data.skipBenchmarks; }
bool Config::benchmarkNoAnalysis() const { return m_data.benchmarkNoAnalysis; }
unsigned int Config::benchmarkSamples() const { return m_data.benchmarkSamples; }
double Config::benchmarkConfidenceInterval() const { return m_data.benchmarkConfidenceInterval; }
unsigned int Config::benchmarkResamples() const { return m_data.benchmarkResamples; }
std::chrono::milliseconds Config::benchmarkWarmupTime() const { return std::chrono::milliseconds(m_data.benchmarkWarmupTime); }
void Config::readBazelEnvVars() {
// Register a JUnit reporter for Bazel. Bazel sets an environment
// variable with the path to XML output. If this file is written to
// during test, Bazel will not generate a default XML output.
// This allows the XML output file to contain higher level of detail
// than what is possible otherwise.
const auto bazelOutputFile = Detail::getEnv( "XML_OUTPUT_FILE" );
if ( bazelOutputFile ) {
m_data.reporterSpecifications.push_back(
{ "junit", std::string( bazelOutputFile ), {}, {} } );
}
const auto bazelTestSpec = Detail::getEnv( "TESTBRIDGE_TEST_ONLY" );
if ( bazelTestSpec ) {
// Presumably the test spec from environment should overwrite
// the one we got from CLI (if we got any)
m_data.testsOrTags.clear();
m_data.testsOrTags.push_back( bazelTestSpec );
}
const auto bazelShardOptions = readBazelShardingOptions();
if ( bazelShardOptions ) {
std::ofstream f( bazelShardOptions->shardFilePath,
std::ios_base::out | std::ios_base::trunc );
if ( f.is_open() ) {
f << "";
m_data.shardIndex = bazelShardOptions->shardIndex;
m_data.shardCount = bazelShardOptions->shardCount;
}
}
}
} // end namespace Catch

View File

@ -0,0 +1,153 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#ifndef CATCH_CONFIG_HPP_INCLUDED
#define CATCH_CONFIG_HPP_INCLUDED
#include <catch2/catch_test_spec.hpp>
#include <catch2/interfaces/catch_interfaces_config.hpp>
#include <catch2/internal/catch_unique_ptr.hpp>
#include <catch2/internal/catch_optional.hpp>
#include <catch2/internal/catch_stringref.hpp>
#include <catch2/internal/catch_random_seed_generation.hpp>
#include <catch2/internal/catch_reporter_spec_parser.hpp>
#include <chrono>
#include <map>
#include <string>
#include <vector>
namespace Catch {
class IStream;
/**
* `ReporterSpec` but with the defaults filled in.
*
* Like `ReporterSpec`, the semantics are unchecked.
*/
struct ProcessedReporterSpec {
std::string name;
std::string outputFilename;
ColourMode colourMode;
std::map<std::string, std::string> customOptions;
friend bool operator==( ProcessedReporterSpec const& lhs,
ProcessedReporterSpec const& rhs );
friend bool operator!=( ProcessedReporterSpec const& lhs,
ProcessedReporterSpec const& rhs ) {
return !( lhs == rhs );
}
};
struct ConfigData {
bool listTests = false;
bool listTags = false;
bool listReporters = false;
bool listListeners = false;
bool showSuccessfulTests = false;
bool shouldDebugBreak = false;
bool noThrow = false;
bool showHelp = false;
bool showInvisibles = false;
bool filenamesAsTags = false;
bool libIdentify = false;
bool allowZeroTests = false;
int abortAfter = -1;
uint32_t rngSeed = generateRandomSeed(GenerateFrom::Default);
unsigned int shardCount = 1;
unsigned int shardIndex = 0;
bool skipBenchmarks = false;
bool benchmarkNoAnalysis = false;
unsigned int benchmarkSamples = 100;
double benchmarkConfidenceInterval = 0.95;
unsigned int benchmarkResamples = 100000;
std::chrono::milliseconds::rep benchmarkWarmupTime = 100;
Verbosity verbosity = Verbosity::Normal;
WarnAbout::What warnings = WarnAbout::Nothing;
ShowDurations showDurations = ShowDurations::DefaultForReporter;
double minDuration = -1;
TestRunOrder runOrder = TestRunOrder::Declared;
ColourMode defaultColourMode = ColourMode::PlatformDefault;
WaitForKeypress::When waitForKeypress = WaitForKeypress::Never;
std::string defaultOutputFilename;
std::string name;
std::string processName;
std::vector<ReporterSpec> reporterSpecifications;
std::vector<std::string> testsOrTags;
std::vector<std::string> sectionsToRun;
};
class Config : public IConfig {
public:
Config() = default;
Config( ConfigData const& data );
~Config() override; // = default in the cpp file
bool listTests() const;
bool listTags() const;
bool listReporters() const;
bool listListeners() const;
std::vector<ReporterSpec> const& getReporterSpecs() const;
std::vector<ProcessedReporterSpec> const&
getProcessedReporterSpecs() const;
std::vector<std::string> const& getTestsOrTags() const override;
std::vector<std::string> const& getSectionsToRun() const override;
TestSpec const& testSpec() const override;
bool hasTestFilters() const override;
bool showHelp() const;
// IConfig interface
bool allowThrows() const override;
StringRef name() const override;
bool includeSuccessfulResults() const override;
bool warnAboutMissingAssertions() const override;
bool warnAboutUnmatchedTestSpecs() const override;
bool zeroTestsCountAsSuccess() const override;
ShowDurations showDurations() const override;
double minDuration() const override;
TestRunOrder runOrder() const override;
uint32_t rngSeed() const override;
unsigned int shardCount() const override;
unsigned int shardIndex() const override;
ColourMode defaultColourMode() const override;
bool shouldDebugBreak() const override;
int abortAfter() const override;
bool showInvisibles() const override;
Verbosity verbosity() const override;
bool skipBenchmarks() const override;
bool benchmarkNoAnalysis() const override;
unsigned int benchmarkSamples() const override;
double benchmarkConfidenceInterval() const override;
unsigned int benchmarkResamples() const override;
std::chrono::milliseconds benchmarkWarmupTime() const override;
private:
// Reads Bazel env vars and applies them to the config
void readBazelEnvVars();
ConfigData m_data;
std::vector<ProcessedReporterSpec> m_processedReporterSpecs;
TestSpec m_testSpec;
bool m_hasTestFilters = false;
};
} // end namespace Catch
#endif // CATCH_CONFIG_HPP_INCLUDED

View File

@ -0,0 +1,18 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/catch_get_random_seed.hpp>
#include <catch2/internal/catch_context.hpp>
#include <catch2/catch_config.hpp>
namespace Catch {
std::uint32_t getSeed() {
return getCurrentContext().getConfig()->rngSeed();
}
}

View File

@ -0,0 +1,18 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#ifndef CATCH_GET_RANDOM_SEED_HPP_INCLUDED
#define CATCH_GET_RANDOM_SEED_HPP_INCLUDED
#include <cstdint>
namespace Catch {
//! Returns Catch2's current RNG seed.
std::uint32_t getSeed();
}
#endif // CATCH_GET_RANDOM_SEED_HPP_INCLUDED

View File

@ -0,0 +1,116 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/catch_message.hpp>
#include <catch2/interfaces/catch_interfaces_capture.hpp>
#include <catch2/internal/catch_uncaught_exceptions.hpp>
#include <catch2/internal/catch_enforce.hpp>
#include <catch2/internal/catch_move_and_forward.hpp>
#include <cassert>
#include <stack>
namespace Catch {
////////////////////////////////////////////////////////////////////////////
ScopedMessage::ScopedMessage( MessageBuilder&& builder ):
m_info( CATCH_MOVE(builder.m_info) ) {
m_info.message = builder.m_stream.str();
getResultCapture().pushScopedMessage( m_info );
}
ScopedMessage::ScopedMessage( ScopedMessage&& old ) noexcept:
m_info( CATCH_MOVE( old.m_info ) ) {
old.m_moved = true;
}
ScopedMessage::~ScopedMessage() {
if ( !uncaught_exceptions() && !m_moved ){
getResultCapture().popScopedMessage(m_info);
}
}
Capturer::Capturer( StringRef macroName,
SourceLineInfo const& lineInfo,
ResultWas::OfType resultType,
StringRef names ):
m_resultCapture( getResultCapture() ) {
auto trimmed = [&] (size_t start, size_t end) {
while (names[start] == ',' || isspace(static_cast<unsigned char>(names[start]))) {
++start;
}
while (names[end] == ',' || isspace(static_cast<unsigned char>(names[end]))) {
--end;
}
return names.substr(start, end - start + 1);
};
auto skipq = [&] (size_t start, char quote) {
for (auto i = start + 1; i < names.size() ; ++i) {
if (names[i] == quote)
return i;
if (names[i] == '\\')
++i;
}
CATCH_INTERNAL_ERROR("CAPTURE parsing encountered unmatched quote");
};
size_t start = 0;
std::stack<char> openings;
for (size_t pos = 0; pos < names.size(); ++pos) {
char c = names[pos];
switch (c) {
case '[':
case '{':
case '(':
// It is basically impossible to disambiguate between
// comparison and start of template args in this context
// case '<':
openings.push(c);
break;
case ']':
case '}':
case ')':
// case '>':
openings.pop();
break;
case '"':
case '\'':
pos = skipq(pos, c);
break;
case ',':
if (start != pos && openings.empty()) {
m_messages.emplace_back(macroName, lineInfo, resultType);
m_messages.back().message = static_cast<std::string>(trimmed(start, pos));
m_messages.back().message += " := ";
start = pos;
}
}
}
assert(openings.empty() && "Mismatched openings");
m_messages.emplace_back(macroName, lineInfo, resultType);
m_messages.back().message = static_cast<std::string>(trimmed(start, names.size() - 1));
m_messages.back().message += " := ";
}
Capturer::~Capturer() {
if ( !uncaught_exceptions() ){
assert( m_captured == m_messages.size() );
for( size_t i = 0; i < m_captured; ++i )
m_resultCapture.popScopedMessage( m_messages[i] );
}
}
void Capturer::captureValue( size_t index, std::string const& value ) {
assert( index < m_messages.size() );
m_messages[index].message += value;
m_resultCapture.pushScopedMessage( m_messages[index] );
m_captured++;
}
} // end namespace Catch

View File

@ -0,0 +1,148 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#ifndef CATCH_MESSAGE_HPP_INCLUDED
#define CATCH_MESSAGE_HPP_INCLUDED
#include <catch2/internal/catch_result_type.hpp>
#include <catch2/internal/catch_reusable_string_stream.hpp>
#include <catch2/internal/catch_stream_end_stop.hpp>
#include <catch2/internal/catch_message_info.hpp>
#include <catch2/catch_tostring.hpp>
#include <string>
#include <vector>
namespace Catch {
struct SourceLineInfo;
class IResultCapture;
struct MessageStream {
template<typename T>
MessageStream& operator << ( T const& value ) {
m_stream << value;
return *this;
}
ReusableStringStream m_stream;
};
struct MessageBuilder : MessageStream {
MessageBuilder( StringRef macroName,
SourceLineInfo const& lineInfo,
ResultWas::OfType type ):
m_info(macroName, lineInfo, type) {}
template<typename T>
MessageBuilder&& operator << ( T const& value ) && {
m_stream << value;
return CATCH_MOVE(*this);
}
MessageInfo m_info;
};
class ScopedMessage {
public:
explicit ScopedMessage( MessageBuilder&& builder );
ScopedMessage( ScopedMessage& duplicate ) = delete;
ScopedMessage( ScopedMessage&& old ) noexcept;
~ScopedMessage();
MessageInfo m_info;
bool m_moved = false;
};
class Capturer {
std::vector<MessageInfo> m_messages;
IResultCapture& m_resultCapture;
size_t m_captured = 0;
public:
Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names );
Capturer(Capturer const&) = delete;
Capturer& operator=(Capturer const&) = delete;
~Capturer();
void captureValue( size_t index, std::string const& value );
template<typename T>
void captureValues( size_t index, T const& value ) {
captureValue( index, Catch::Detail::stringify( value ) );
}
template<typename T, typename... Ts>
void captureValues( size_t index, T const& value, Ts const&... values ) {
captureValue( index, Catch::Detail::stringify(value) );
captureValues( index+1, values... );
}
};
} // end namespace Catch
///////////////////////////////////////////////////////////////////////////////
#define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \
do { \
Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::StringRef(), resultDisposition ); \
catchAssertionHandler.handleMessage( messageType, ( Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop() ).m_stream.str() ); \
INTERNAL_CATCH_REACT( catchAssertionHandler ) \
} while( false )
///////////////////////////////////////////////////////////////////////////////
#define INTERNAL_CATCH_CAPTURE( varName, macroName, ... ) \
Catch::Capturer varName( macroName##_catch_sr, \
CATCH_INTERNAL_LINEINFO, \
Catch::ResultWas::Info, \
#__VA_ARGS__##_catch_sr ); \
varName.captureValues( 0, __VA_ARGS__ )
///////////////////////////////////////////////////////////////////////////////
#define INTERNAL_CATCH_INFO( macroName, log ) \
const Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage )( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log )
///////////////////////////////////////////////////////////////////////////////
#define INTERNAL_CATCH_UNSCOPED_INFO( macroName, log ) \
Catch::getResultCapture().emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log )
#if defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE)
#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg )
#define CATCH_UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( "CATCH_UNSCOPED_INFO", msg )
#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg )
#define CATCH_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_CAPTURE", __VA_ARGS__ )
#elif defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE)
#define CATCH_INFO( msg ) (void)(0)
#define CATCH_UNSCOPED_INFO( msg ) (void)(0)
#define CATCH_WARN( msg ) (void)(0)
#define CATCH_CAPTURE( ... ) (void)(0)
#elif !defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE)
#define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg )
#define UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( "UNSCOPED_INFO", msg )
#define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg )
#define CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CAPTURE", __VA_ARGS__ )
#elif !defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE)
#define INFO( msg ) (void)(0)
#define UNSCOPED_INFO( msg ) (void)(0)
#define WARN( msg ) (void)(0)
#define CAPTURE( ... ) (void)(0)
#endif // end of user facing macro declarations
#endif // CATCH_MESSAGE_HPP_INCLUDED

View File

@ -0,0 +1,107 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/interfaces/catch_interfaces_registry_hub.hpp>
#include <catch2/internal/catch_context.hpp>
#include <catch2/internal/catch_enforce.hpp>
#include <catch2/internal/catch_test_case_registry_impl.hpp>
#include <catch2/internal/catch_reporter_registry.hpp>
#include <catch2/internal/catch_exception_translator_registry.hpp>
#include <catch2/internal/catch_tag_alias_registry.hpp>
#include <catch2/internal/catch_startup_exception_registry.hpp>
#include <catch2/internal/catch_singletons.hpp>
#include <catch2/internal/catch_enum_values_registry.hpp>
#include <catch2/catch_test_case_info.hpp>
#include <catch2/internal/catch_noncopyable.hpp>
#include <catch2/interfaces/catch_interfaces_reporter_factory.hpp>
#include <catch2/internal/catch_move_and_forward.hpp>
#include <catch2/internal/catch_reporter_registry.hpp>
#include <exception>
namespace Catch {
namespace {
class RegistryHub : public IRegistryHub,
public IMutableRegistryHub,
private Detail::NonCopyable {
public: // IRegistryHub
RegistryHub() = default;
ReporterRegistry const& getReporterRegistry() const override {
return m_reporterRegistry;
}
ITestCaseRegistry const& getTestCaseRegistry() const override {
return m_testCaseRegistry;
}
IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const override {
return m_exceptionTranslatorRegistry;
}
ITagAliasRegistry const& getTagAliasRegistry() const override {
return m_tagAliasRegistry;
}
StartupExceptionRegistry const& getStartupExceptionRegistry() const override {
return m_exceptionRegistry;
}
public: // IMutableRegistryHub
void registerReporter( std::string const& name, IReporterFactoryPtr factory ) override {
m_reporterRegistry.registerReporter( name, CATCH_MOVE(factory) );
}
void registerListener( Detail::unique_ptr<EventListenerFactory> factory ) override {
m_reporterRegistry.registerListener( CATCH_MOVE(factory) );
}
void registerTest( Detail::unique_ptr<TestCaseInfo>&& testInfo, Detail::unique_ptr<ITestInvoker>&& invoker ) override {
m_testCaseRegistry.registerTest( CATCH_MOVE(testInfo), CATCH_MOVE(invoker) );
}
void registerTranslator( Detail::unique_ptr<IExceptionTranslator>&& translator ) override {
m_exceptionTranslatorRegistry.registerTranslator( CATCH_MOVE(translator) );
}
void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) override {
m_tagAliasRegistry.add( alias, tag, lineInfo );
}
void registerStartupException() noexcept override {
#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
m_exceptionRegistry.add(std::current_exception());
#else
CATCH_INTERNAL_ERROR("Attempted to register active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!");
#endif
}
IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() override {
return m_enumValuesRegistry;
}
private:
TestRegistry m_testCaseRegistry;
ReporterRegistry m_reporterRegistry;
ExceptionTranslatorRegistry m_exceptionTranslatorRegistry;
TagAliasRegistry m_tagAliasRegistry;
StartupExceptionRegistry m_exceptionRegistry;
Detail::EnumValuesRegistry m_enumValuesRegistry;
};
}
using RegistryHubSingleton = Singleton<RegistryHub, IRegistryHub, IMutableRegistryHub>;
IRegistryHub const& getRegistryHub() {
return RegistryHubSingleton::get();
}
IMutableRegistryHub& getMutableRegistryHub() {
return RegistryHubSingleton::getMutable();
}
void cleanUp() {
cleanupSingletons();
cleanUpContext();
}
std::string translateActiveException() {
return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException();
}
} // end namespace Catch

View File

@ -0,0 +1,42 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#ifndef CATCH_SECTION_INFO_HPP_INCLUDED
#define CATCH_SECTION_INFO_HPP_INCLUDED
#include <catch2/internal/catch_move_and_forward.hpp>
#include <catch2/internal/catch_source_line_info.hpp>
#include <catch2/internal/catch_stringref.hpp>
#include <catch2/catch_totals.hpp>
#include <string>
namespace Catch {
struct SectionInfo {
// The last argument is ignored, so that people can write
// SECTION("ShortName", "Proper description that is long") and
// still use the `-c` flag comfortably.
SectionInfo( SourceLineInfo const& _lineInfo, std::string _name,
const char* const = nullptr ):
name(CATCH_MOVE(_name)),
lineInfo(_lineInfo)
{}
std::string name;
SourceLineInfo lineInfo;
};
struct SectionEndInfo {
SectionInfo sectionInfo;
Counts prevAssertions;
double durationInSeconds;
};
} // end namespace Catch
#endif // CATCH_SECTION_INFO_HPP_INCLUDED

View File

@ -0,0 +1,364 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/catch_session.hpp>
#include <catch2/internal/catch_console_colour.hpp>
#include <catch2/internal/catch_enforce.hpp>
#include <catch2/internal/catch_list.hpp>
#include <catch2/internal/catch_context.hpp>
#include <catch2/internal/catch_run_context.hpp>
#include <catch2/catch_test_spec.hpp>
#include <catch2/catch_version.hpp>
#include <catch2/internal/catch_startup_exception_registry.hpp>
#include <catch2/internal/catch_sharding.hpp>
#include <catch2/internal/catch_test_case_registry_impl.hpp>
#include <catch2/internal/catch_textflow.hpp>
#include <catch2/internal/catch_windows_h_proxy.hpp>
#include <catch2/reporters/catch_reporter_multi.hpp>
#include <catch2/internal/catch_reporter_registry.hpp>
#include <catch2/interfaces/catch_interfaces_reporter_factory.hpp>
#include <catch2/internal/catch_move_and_forward.hpp>
#include <catch2/internal/catch_stdstreams.hpp>
#include <catch2/internal/catch_istream.hpp>
#include <algorithm>
#include <cassert>
#include <exception>
#include <iomanip>
#include <set>
namespace Catch {
namespace {
const int MaxExitCode = 255;
IEventListenerPtr createReporter(std::string const& reporterName, ReporterConfig&& config) {
auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, CATCH_MOVE(config));
CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << '\'');
return reporter;
}
IEventListenerPtr prepareReporters(Config const* config) {
if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty()
&& config->getProcessedReporterSpecs().size() == 1) {
auto const& spec = config->getProcessedReporterSpecs()[0];
return createReporter(
spec.name,
ReporterConfig( config,
makeStream( spec.outputFilename ),
spec.colourMode,
spec.customOptions ) );
}
auto multi = Detail::make_unique<MultiReporter>(config);
auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners();
for (auto const& listener : listeners) {
multi->addListener(listener->create(config));
}
for ( auto const& reporterSpec : config->getProcessedReporterSpecs() ) {
multi->addReporter( createReporter(
reporterSpec.name,
ReporterConfig( config,
makeStream( reporterSpec.outputFilename ),
reporterSpec.colourMode,
reporterSpec.customOptions ) ) );
}
return multi;
}
class TestGroup {
public:
explicit TestGroup(IEventListenerPtr&& reporter, Config const* config):
m_reporter(reporter.get()),
m_config{config},
m_context{config, CATCH_MOVE(reporter)} {
assert( m_config->testSpec().getInvalidSpecs().empty() &&
"Invalid test specs should be handled before running tests" );
auto const& allTestCases = getAllTestCasesSorted(*m_config);
auto const& testSpec = m_config->testSpec();
if ( !testSpec.hasFilters() ) {
for ( auto const& test : allTestCases ) {
if ( !test.getTestCaseInfo().isHidden() ) {
m_tests.emplace( &test );
}
}
} else {
m_matches =
testSpec.matchesByFilter( allTestCases, *m_config );
for ( auto const& match : m_matches ) {
m_tests.insert( match.tests.begin(),
match.tests.end() );
}
}
m_tests = createShard(m_tests, m_config->shardCount(), m_config->shardIndex());
}
Totals execute() {
Totals totals;
for (auto const& testCase : m_tests) {
if (!m_context.aborting())
totals += m_context.runTest(*testCase);
else
m_reporter->skipTest(testCase->getTestCaseInfo());
}
for (auto const& match : m_matches) {
if (match.tests.empty()) {
m_unmatchedTestSpecs = true;
m_reporter->noMatchingTestCases( match.name );
}
}
return totals;
}
bool hadUnmatchedTestSpecs() const {
return m_unmatchedTestSpecs;
}
private:
IEventListener* m_reporter;
Config const* m_config;
RunContext m_context;
std::set<TestCaseHandle const*> m_tests;
TestSpec::Matches m_matches;
bool m_unmatchedTestSpecs = false;
};
void applyFilenamesAsTags() {
for (auto const& testInfo : getRegistryHub().getTestCaseRegistry().getAllInfos()) {
testInfo->addFilenameTag();
}
}
} // anon namespace
Session::Session() {
static bool alreadyInstantiated = false;
if( alreadyInstantiated ) {
CATCH_TRY { CATCH_INTERNAL_ERROR( "Only one instance of Catch::Session can ever be used" ); }
CATCH_CATCH_ALL { getMutableRegistryHub().registerStartupException(); }
}
// There cannot be exceptions at startup in no-exception mode.
#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions();
if ( !exceptions.empty() ) {
config();
getCurrentMutableContext().setConfig(m_config.get());
m_startupExceptions = true;
auto errStream = makeStream( "%stderr" );
auto colourImpl = makeColourImpl(
ColourMode::PlatformDefault, errStream.get() );
auto guard = colourImpl->guardColour( Colour::Red );
errStream->stream() << "Errors occurred during startup!" << '\n';
// iterate over all exceptions and notify user
for ( const auto& ex_ptr : exceptions ) {
try {
std::rethrow_exception(ex_ptr);
} catch ( std::exception const& ex ) {
errStream->stream() << TextFlow::Column( ex.what() ).indent(2) << '\n';
}
}
}
#endif
alreadyInstantiated = true;
m_cli = makeCommandLineParser( m_configData );
}
Session::~Session() {
Catch::cleanUp();
}
void Session::showHelp() const {
Catch::cout()
<< "\nCatch2 v" << libraryVersion() << '\n'
<< m_cli << '\n'
<< "For more detailed usage please see the project docs\n\n" << std::flush;
}
void Session::libIdentify() {
Catch::cout()
<< std::left << std::setw(16) << "description: " << "A Catch2 test executable\n"
<< std::left << std::setw(16) << "category: " << "testframework\n"
<< std::left << std::setw(16) << "framework: " << "Catch2\n"
<< std::left << std::setw(16) << "version: " << libraryVersion() << '\n' << std::flush;
}
int Session::applyCommandLine( int argc, char const * const * argv ) {
if( m_startupExceptions )
return 1;
auto result = m_cli.parse( Clara::Args( argc, argv ) );
if( !result ) {
config();
getCurrentMutableContext().setConfig(m_config.get());
auto errStream = makeStream( "%stderr" );
auto colour = makeColourImpl( ColourMode::PlatformDefault, errStream.get() );
errStream->stream()
<< colour->guardColour( Colour::Red )
<< "\nError(s) in input:\n"
<< TextFlow::Column( result.errorMessage() ).indent( 2 )
<< "\n\n";
errStream->stream() << "Run with -? for usage\n\n" << std::flush;
return MaxExitCode;
}
if( m_configData.showHelp )
showHelp();
if( m_configData.libIdentify )
libIdentify();
m_config.reset();
return 0;
}
#if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE)
int Session::applyCommandLine( int argc, wchar_t const * const * argv ) {
char **utf8Argv = new char *[ argc ];
for ( int i = 0; i < argc; ++i ) {
int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, nullptr, 0, nullptr, nullptr );
utf8Argv[ i ] = new char[ bufSize ];
WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, nullptr, nullptr );
}
int returnCode = applyCommandLine( argc, utf8Argv );
for ( int i = 0; i < argc; ++i )
delete [] utf8Argv[ i ];
delete [] utf8Argv;
return returnCode;
}
#endif
void Session::useConfigData( ConfigData const& configData ) {
m_configData = configData;
m_config.reset();
}
int Session::run() {
if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) {
Catch::cout() << "...waiting for enter/ return before starting\n" << std::flush;
static_cast<void>(std::getchar());
}
int exitCode = runInternal();
if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) {
Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << '\n' << std::flush;
static_cast<void>(std::getchar());
}
return exitCode;
}
Clara::Parser const& Session::cli() const {
return m_cli;
}
void Session::cli( Clara::Parser const& newParser ) {
m_cli = newParser;
}
ConfigData& Session::configData() {
return m_configData;
}
Config& Session::config() {
if( !m_config )
m_config = Detail::make_unique<Config>( m_configData );
return *m_config;
}
int Session::runInternal() {
if( m_startupExceptions )
return 1;
if (m_configData.showHelp || m_configData.libIdentify) {
return 0;
}
if ( m_configData.shardIndex >= m_configData.shardCount ) {
Catch::cerr() << "The shard count (" << m_configData.shardCount
<< ") must be greater than the shard index ("
<< m_configData.shardIndex << ")\n"
<< std::flush;
return 1;
}
CATCH_TRY {
config(); // Force config to be constructed
seedRng( *m_config );
if (m_configData.filenamesAsTags) {
applyFilenamesAsTags();
}
// Set up global config instance before we start calling into other functions
getCurrentMutableContext().setConfig(m_config.get());
// Create reporter(s) so we can route listings through them
auto reporter = prepareReporters(m_config.get());
auto const& invalidSpecs = m_config->testSpec().getInvalidSpecs();
if ( !invalidSpecs.empty() ) {
for ( auto const& spec : invalidSpecs ) {
reporter->reportInvalidTestSpec( spec );
}
return 1;
}
// Handle list request
if (list(*reporter, *m_config)) {
return 0;
}
TestGroup tests { CATCH_MOVE(reporter), m_config.get() };
auto const totals = tests.execute();
if ( tests.hadUnmatchedTestSpecs()
&& m_config->warnAboutUnmatchedTestSpecs() ) {
return 3;
}
if ( totals.testCases.total() == 0
&& !m_config->zeroTestsCountAsSuccess() ) {
return 2;
}
if ( totals.testCases.total() > 0 &&
totals.testCases.total() == totals.testCases.skipped
&& !m_config->zeroTestsCountAsSuccess() ) {
return 4;
}
// Note that on unices only the lower 8 bits are usually used, clamping
// the return value to 255 prevents false negative when some multiple
// of 256 tests has failed
return (std::min) (MaxExitCode, static_cast<int>(totals.assertions.failed));
}
#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
catch( std::exception& ex ) {
Catch::cerr() << ex.what() << '\n' << std::flush;
return MaxExitCode;
}
#endif
}
} // end namespace Catch

View File

@ -0,0 +1,62 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#ifndef CATCH_SESSION_HPP_INCLUDED
#define CATCH_SESSION_HPP_INCLUDED
#include <catch2/internal/catch_commandline.hpp>
#include <catch2/internal/catch_noncopyable.hpp>
#include <catch2/catch_config.hpp>
#include <catch2/internal/catch_unique_ptr.hpp>
#include <catch2/internal/catch_config_wchar.hpp>
namespace Catch {
class Session : Detail::NonCopyable {
public:
Session();
~Session();
void showHelp() const;
void libIdentify();
int applyCommandLine( int argc, char const * const * argv );
#if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE)
int applyCommandLine( int argc, wchar_t const * const * argv );
#endif
void useConfigData( ConfigData const& configData );
template<typename CharT>
int run(int argc, CharT const * const argv[]) {
if (m_startupExceptions)
return 1;
int returnCode = applyCommandLine(argc, argv);
if (returnCode == 0)
returnCode = run();
return returnCode;
}
int run();
Clara::Parser const& cli() const;
void cli( Clara::Parser const& newParser );
ConfigData& configData();
Config& config();
private:
int runInternal();
Clara::Parser m_cli;
ConfigData m_configData;
Detail::unique_ptr<Config> m_config;
bool m_startupExceptions = false;
};
} // end namespace Catch
#endif // CATCH_SESSION_HPP_INCLUDED

View File

@ -0,0 +1,29 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#ifndef CATCH_TAG_ALIAS_HPP_INCLUDED
#define CATCH_TAG_ALIAS_HPP_INCLUDED
#include <catch2/internal/catch_source_line_info.hpp>
#include <string>
namespace Catch {
struct TagAlias {
TagAlias(std::string const& _tag, SourceLineInfo _lineInfo):
tag(_tag),
lineInfo(_lineInfo)
{}
std::string tag;
SourceLineInfo lineInfo;
};
} // end namespace Catch
#endif // CATCH_TAG_ALIAS_HPP_INCLUDED

View File

@ -0,0 +1,24 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/catch_tag_alias_autoregistrar.hpp>
#include <catch2/internal/catch_compiler_capabilities.hpp>
#include <catch2/interfaces/catch_interfaces_registry_hub.hpp>
namespace Catch {
RegistrarForTagAliases::RegistrarForTagAliases(char const* alias, char const* tag, SourceLineInfo const& lineInfo) {
CATCH_TRY {
getMutableRegistryHub().registerTagAlias(alias, tag, lineInfo);
} CATCH_CATCH_ALL {
// Do not throw when constructing global objects, instead register the exception to be processed later
getMutableRegistryHub().registerStartupException();
}
}
}

View File

@ -0,0 +1,29 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#ifndef CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED
#define CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED
#include <catch2/internal/catch_compiler_capabilities.hpp>
#include <catch2/internal/catch_unique_name.hpp>
#include <catch2/internal/catch_source_line_info.hpp>
namespace Catch {
struct RegistrarForTagAliases {
RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo );
};
} // end namespace Catch
#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \
CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \
CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION
#endif // CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED

View File

@ -0,0 +1,124 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#ifndef CATCH_TEMPLATE_TEST_MACROS_HPP_INCLUDED
#define CATCH_TEMPLATE_TEST_MACROS_HPP_INCLUDED
// We need this suppression to leak, because it took until GCC 10
// for the front end to handle local suppression via _Pragma properly
// inside templates (so `TEMPLATE_TEST_CASE` and co).
// **THIS IS DIFFERENT FOR STANDARD TESTS, WHERE GCC 9 IS SUFFICIENT**
#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && __GNUC__ < 10
#pragma GCC diagnostic ignored "-Wparentheses"
#endif
#include <catch2/catch_test_macros.hpp>
#include <catch2/internal/catch_template_test_registry.hpp>
#include <catch2/internal/catch_preprocessor.hpp>
#if defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE)
#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ )
#define CATCH_TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG( __VA_ARGS__ )
#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )
#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ )
#define CATCH_TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ )
#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( __VA_ARGS__ )
#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ )
#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ )
#define CATCH_TEMPLATE_LIST_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE(__VA_ARGS__)
#define CATCH_TEMPLATE_LIST_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD( className, __VA_ARGS__ )
#else
#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) )
#define CATCH_TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG( __VA_ARGS__ ) )
#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) )
#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) )
#define CATCH_TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ ) )
#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( __VA_ARGS__ ) )
#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ ) )
#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) )
#define CATCH_TEMPLATE_LIST_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE( __VA_ARGS__ ) )
#define CATCH_TEMPLATE_LIST_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD( className, __VA_ARGS__ ) )
#endif
#elif defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE)
#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__)
#define CATCH_TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__)
#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__)
#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__ )
#else
#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__) )
#define CATCH_TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__) )
#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__ ) )
#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__ ) )
#endif
// When disabled, these can be shared between proper preprocessor and MSVC preprocessor
#define CATCH_TEMPLATE_PRODUCT_TEST_CASE( ... ) CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ )
#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ )
#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )
#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )
#define CATCH_TEMPLATE_LIST_TEST_CASE( ... ) CATCH_TEMPLATE_TEST_CASE(__VA_ARGS__)
#define CATCH_TEMPLATE_LIST_TEST_CASE_METHOD( className, ... ) CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )
#elif !defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE)
#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ )
#define TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG( __VA_ARGS__ )
#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )
#define TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ )
#define TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ )
#define TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( __VA_ARGS__ )
#define TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ )
#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ )
#define TEMPLATE_LIST_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE(__VA_ARGS__)
#define TEMPLATE_LIST_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD( className, __VA_ARGS__ )
#else
#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) )
#define TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG( __VA_ARGS__ ) )
#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) )
#define TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) )
#define TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ ) )
#define TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( __VA_ARGS__ ) )
#define TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ ) )
#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) )
#define TEMPLATE_LIST_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE( __VA_ARGS__ ) )
#define TEMPLATE_LIST_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD( className, __VA_ARGS__ ) )
#endif
#elif !defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE)
#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__)
#define TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__)
#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__)
#define TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__ )
#else
#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__) )
#define TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__) )
#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__ ) )
#define TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__ ) )
#endif
// When disabled, these can be shared between proper preprocessor and MSVC preprocessor
#define TEMPLATE_PRODUCT_TEST_CASE( ... ) TEMPLATE_TEST_CASE( __VA_ARGS__ )
#define TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) TEMPLATE_TEST_CASE( __VA_ARGS__ )
#define TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )
#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )
#define TEMPLATE_LIST_TEST_CASE( ... ) TEMPLATE_TEST_CASE(__VA_ARGS__)
#define TEMPLATE_LIST_TEST_CASE_METHOD( className, ... ) TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )
#endif // end of user facing macro declarations
#endif // CATCH_TEMPLATE_TEST_MACROS_HPP_INCLUDED

View File

@ -0,0 +1,263 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/catch_test_case_info.hpp>
#include <catch2/internal/catch_enforce.hpp>
#include <catch2/internal/catch_string_manip.hpp>
#include <catch2/internal/catch_case_insensitive_comparisons.hpp>
#include <cassert>
#include <cctype>
#include <algorithm>
namespace Catch {
namespace {
using TCP_underlying_type = uint8_t;
static_assert(sizeof(TestCaseProperties) == sizeof(TCP_underlying_type),
"The size of the TestCaseProperties is different from the assumed size");
TestCaseProperties operator|(TestCaseProperties lhs, TestCaseProperties rhs) {
return static_cast<TestCaseProperties>(
static_cast<TCP_underlying_type>(lhs) | static_cast<TCP_underlying_type>(rhs)
);
}
TestCaseProperties& operator|=(TestCaseProperties& lhs, TestCaseProperties rhs) {
lhs = static_cast<TestCaseProperties>(
static_cast<TCP_underlying_type>(lhs) | static_cast<TCP_underlying_type>(rhs)
);
return lhs;
}
TestCaseProperties operator&(TestCaseProperties lhs, TestCaseProperties rhs) {
return static_cast<TestCaseProperties>(
static_cast<TCP_underlying_type>(lhs) & static_cast<TCP_underlying_type>(rhs)
);
}
bool applies(TestCaseProperties tcp) {
static_assert(static_cast<TCP_underlying_type>(TestCaseProperties::None) == 0,
"TestCaseProperties::None must be equal to 0");
return tcp != TestCaseProperties::None;
}
TestCaseProperties parseSpecialTag( StringRef tag ) {
if( !tag.empty() && tag[0] == '.' )
return TestCaseProperties::IsHidden;
else if( tag == "!throws"_sr )
return TestCaseProperties::Throws;
else if( tag == "!shouldfail"_sr )
return TestCaseProperties::ShouldFail;
else if( tag == "!mayfail"_sr )
return TestCaseProperties::MayFail;
else if( tag == "!nonportable"_sr )
return TestCaseProperties::NonPortable;
else if( tag == "!benchmark"_sr )
return TestCaseProperties::Benchmark | TestCaseProperties::IsHidden;
else
return TestCaseProperties::None;
}
bool isReservedTag( StringRef tag ) {
return parseSpecialTag( tag ) == TestCaseProperties::None
&& tag.size() > 0
&& !std::isalnum( static_cast<unsigned char>(tag[0]) );
}
void enforceNotReservedTag( StringRef tag, SourceLineInfo const& _lineInfo ) {
CATCH_ENFORCE( !isReservedTag(tag),
"Tag name: [" << tag << "] is not allowed.\n"
<< "Tag names starting with non alphanumeric characters are reserved\n"
<< _lineInfo );
}
std::string makeDefaultName() {
static size_t counter = 0;
return "Anonymous test case " + std::to_string(++counter);
}
StringRef extractFilenamePart(StringRef filename) {
size_t lastDot = filename.size();
while (lastDot > 0 && filename[lastDot - 1] != '.') {
--lastDot;
}
--lastDot;
size_t nameStart = lastDot;
while (nameStart > 0 && filename[nameStart - 1] != '/' && filename[nameStart - 1] != '\\') {
--nameStart;
}
return filename.substr(nameStart, lastDot - nameStart);
}
// Returns the upper bound on size of extra tags ([#file]+[.])
size_t sizeOfExtraTags(StringRef filepath) {
// [.] is 3, [#] is another 3
const size_t extras = 3 + 3;
return extractFilenamePart(filepath).size() + extras;
}
} // end unnamed namespace
bool operator<( Tag const& lhs, Tag const& rhs ) {
Detail::CaseInsensitiveLess cmp;
return cmp( lhs.original, rhs.original );
}
bool operator==( Tag const& lhs, Tag const& rhs ) {
Detail::CaseInsensitiveEqualTo cmp;
return cmp( lhs.original, rhs.original );
}
Detail::unique_ptr<TestCaseInfo>
makeTestCaseInfo(StringRef _className,
NameAndTags const& nameAndTags,
SourceLineInfo const& _lineInfo ) {
return Detail::make_unique<TestCaseInfo>(_className, nameAndTags, _lineInfo);
}
TestCaseInfo::TestCaseInfo(StringRef _className,
NameAndTags const& _nameAndTags,
SourceLineInfo const& _lineInfo):
name( _nameAndTags.name.empty() ? makeDefaultName() : _nameAndTags.name ),
className( _className ),
lineInfo( _lineInfo )
{
StringRef originalTags = _nameAndTags.tags;
// We need to reserve enough space to store all of the tags
// (including optional hidden tag and filename tag)
auto requiredSize = originalTags.size() + sizeOfExtraTags(_lineInfo.file);
backingTags.reserve(requiredSize);
// We cannot copy the tags directly, as we need to normalize
// some tags, so that [.foo] is copied as [.][foo].
size_t tagStart = 0;
size_t tagEnd = 0;
bool inTag = false;
for (size_t idx = 0; idx < originalTags.size(); ++idx) {
auto c = originalTags[idx];
if (c == '[') {
CATCH_ENFORCE(
!inTag,
"Found '[' inside a tag while registering test case '"
<< _nameAndTags.name << "' at " << _lineInfo );
inTag = true;
tagStart = idx;
}
if (c == ']') {
CATCH_ENFORCE(
inTag,
"Found unmatched ']' while registering test case '"
<< _nameAndTags.name << "' at " << _lineInfo );
inTag = false;
tagEnd = idx;
assert(tagStart < tagEnd);
// We need to check the tag for special meanings, copy
// it over to backing storage and actually reference the
// backing storage in the saved tags
StringRef tagStr = originalTags.substr(tagStart+1, tagEnd - tagStart - 1);
CATCH_ENFORCE( !tagStr.empty(),
"Found an empty tag while registering test case '"
<< _nameAndTags.name << "' at "
<< _lineInfo );
enforceNotReservedTag(tagStr, lineInfo);
properties |= parseSpecialTag(tagStr);
// When copying a tag to the backing storage, we need to
// check if it is a merged hide tag, such as [.foo], and
// if it is, we need to handle it as if it was [foo].
if (tagStr.size() > 1 && tagStr[0] == '.') {
tagStr = tagStr.substr(1, tagStr.size() - 1);
}
// We skip over dealing with the [.] tag, as we will add
// it later unconditionally and then sort and unique all
// the tags.
internalAppendTag(tagStr);
}
}
CATCH_ENFORCE( !inTag,
"Found an unclosed tag while registering test case '"
<< _nameAndTags.name << "' at " << _lineInfo );
// Add [.] if relevant
if (isHidden()) {
internalAppendTag("."_sr);
}
// Sort and prepare tags
std::sort(begin(tags), end(tags));
tags.erase(std::unique(begin(tags), end(tags)),
end(tags));
}
bool TestCaseInfo::isHidden() const {
return applies( properties & TestCaseProperties::IsHidden );
}
bool TestCaseInfo::throws() const {
return applies( properties & TestCaseProperties::Throws );
}
bool TestCaseInfo::okToFail() const {
return applies( properties & (TestCaseProperties::ShouldFail | TestCaseProperties::MayFail ) );
}
bool TestCaseInfo::expectedToFail() const {
return applies( properties & (TestCaseProperties::ShouldFail) );
}
void TestCaseInfo::addFilenameTag() {
std::string combined("#");
combined += extractFilenamePart(lineInfo.file);
internalAppendTag(combined);
}
std::string TestCaseInfo::tagsAsString() const {
std::string ret;
// '[' and ']' per tag
std::size_t full_size = 2 * tags.size();
for (const auto& tag : tags) {
full_size += tag.original.size();
}
ret.reserve(full_size);
for (const auto& tag : tags) {
ret.push_back('[');
ret += tag.original;
ret.push_back(']');
}
return ret;
}
void TestCaseInfo::internalAppendTag(StringRef tagStr) {
backingTags += '[';
const auto backingStart = backingTags.size();
backingTags += tagStr;
const auto backingEnd = backingTags.size();
backingTags += ']';
tags.emplace_back(StringRef(backingTags.c_str() + backingStart, backingEnd - backingStart));
}
bool operator<( TestCaseInfo const& lhs, TestCaseInfo const& rhs ) {
// We want to avoid redoing the string comparisons multiple times,
// so we store the result of a three-way comparison before using
// it in the actual comparison logic.
const auto cmpName = lhs.name.compare( rhs.name );
if ( cmpName != 0 ) {
return cmpName < 0;
}
const auto cmpClassName = lhs.className.compare( rhs.className );
if ( cmpClassName != 0 ) {
return cmpClassName < 0;
}
return lhs.tags < rhs.tags;
}
TestCaseInfo const& TestCaseHandle::getTestCaseInfo() const {
return *m_info;
}
} // end namespace Catch

View File

@ -0,0 +1,131 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#ifndef CATCH_TEST_CASE_INFO_HPP_INCLUDED
#define CATCH_TEST_CASE_INFO_HPP_INCLUDED
#include <catch2/internal/catch_source_line_info.hpp>
#include <catch2/internal/catch_noncopyable.hpp>
#include <catch2/internal/catch_stringref.hpp>
#include <catch2/internal/catch_test_registry.hpp>
#include <catch2/internal/catch_unique_ptr.hpp>
#include <cstdint>
#include <string>
#include <vector>
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpadded"
#endif
namespace Catch {
/**
* A **view** of a tag string that provides case insensitive comparisons
*
* Note that in Catch2 internals, the square brackets around tags are
* not a part of tag's representation, so e.g. "[cool-tag]" is represented
* as "cool-tag" internally.
*/
struct Tag {
constexpr Tag(StringRef original_):
original(original_)
{}
StringRef original;
friend bool operator< ( Tag const& lhs, Tag const& rhs );
friend bool operator==( Tag const& lhs, Tag const& rhs );
};
class ITestInvoker;
enum class TestCaseProperties : uint8_t {
None = 0,
IsHidden = 1 << 1,
ShouldFail = 1 << 2,
MayFail = 1 << 3,
Throws = 1 << 4,
NonPortable = 1 << 5,
Benchmark = 1 << 6
};
/**
* Various metadata about the test case.
*
* A test case is uniquely identified by its (class)name and tags
* combination, with source location being ignored, and other properties
* being determined from tags.
*
* Tags are kept sorted.
*/
struct TestCaseInfo : Detail::NonCopyable {
TestCaseInfo(StringRef _className,
NameAndTags const& _tags,
SourceLineInfo const& _lineInfo);
bool isHidden() const;
bool throws() const;
bool okToFail() const;
bool expectedToFail() const;
// Adds the tag(s) with test's filename (for the -# flag)
void addFilenameTag();
//! Orders by name, classname and tags
friend bool operator<( TestCaseInfo const& lhs,
TestCaseInfo const& rhs );
std::string tagsAsString() const;
std::string name;
StringRef className;
private:
std::string backingTags;
// Internally we copy tags to the backing storage and then add
// refs to this storage to the tags vector.
void internalAppendTag(StringRef tagString);
public:
std::vector<Tag> tags;
SourceLineInfo lineInfo;
TestCaseProperties properties = TestCaseProperties::None;
};
/**
* Wrapper over the test case information and the test case invoker
*
* Does not own either, and is specifically made to be cheap
* to copy around.
*/
class TestCaseHandle {
TestCaseInfo* m_info;
ITestInvoker* m_invoker;
public:
TestCaseHandle(TestCaseInfo* info, ITestInvoker* invoker) :
m_info(info), m_invoker(invoker) {}
void invoke() const {
m_invoker->invoke();
}
TestCaseInfo const& getTestCaseInfo() const;
};
Detail::unique_ptr<TestCaseInfo>
makeTestCaseInfo( StringRef className,
NameAndTags const& nameAndTags,
SourceLineInfo const& lineInfo );
}
#ifdef __clang__
#pragma clang diagnostic pop
#endif
#endif // CATCH_TEST_CASE_INFO_HPP_INCLUDED

View File

@ -0,0 +1,226 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#ifndef CATCH_TEST_MACROS_HPP_INCLUDED
#define CATCH_TEST_MACROS_HPP_INCLUDED
#include <catch2/internal/catch_test_macro_impl.hpp>
#include <catch2/catch_message.hpp>
#include <catch2/catch_user_config.hpp>
#include <catch2/internal/catch_section.hpp>
#include <catch2/internal/catch_test_registry.hpp>
#include <catch2/internal/catch_unique_name.hpp>
// All of our user-facing macros support configuration toggle, that
// forces them to be defined prefixed with CATCH_. We also like to
// support another toggle that can minimize (disable) their implementation.
// Given this, we have 4 different configuration options below
#if defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE)
#define CATCH_REQUIRE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ )
#define CATCH_REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ )
#define CATCH_REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__ )
#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr )
#define CATCH_REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ )
#define CATCH_CHECK( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
#define CATCH_CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ )
#define CATCH_CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ )
#define CATCH_CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ )
#define CATCH_CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ )
#define CATCH_CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr )
#define CATCH_CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ )
#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ )
#define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )
#define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ )
#define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ )
#define CATCH_DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ )
#define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ )
#define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
#define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
#define CATCH_SKIP( ... ) INTERNAL_CATCH_MSG( "SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ )
#if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE)
#define CATCH_STATIC_REQUIRE( ... ) static_assert( __VA_ARGS__ , #__VA_ARGS__ ); CATCH_SUCCEED( #__VA_ARGS__ )
#define CATCH_STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); CATCH_SUCCEED( #__VA_ARGS__ )
#define CATCH_STATIC_CHECK( ... ) static_assert( __VA_ARGS__ , #__VA_ARGS__ ); CATCH_SUCCEED( #__VA_ARGS__ )
#define CATCH_STATIC_CHECK_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); CATCH_SUCCEED( #__VA_ARGS__ )
#else
#define CATCH_STATIC_REQUIRE( ... ) CATCH_REQUIRE( __VA_ARGS__ )
#define CATCH_STATIC_REQUIRE_FALSE( ... ) CATCH_REQUIRE_FALSE( __VA_ARGS__ )
#define CATCH_STATIC_CHECK( ... ) CATCH_CHECK( __VA_ARGS__ )
#define CATCH_STATIC_CHECK_FALSE( ... ) CATCH_CHECK_FALSE( __VA_ARGS__ )
#endif
// "BDD-style" convenience wrappers
#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ )
#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ )
#define CATCH_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc )
#define CATCH_AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And given: " << desc )
#define CATCH_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc )
#define CATCH_AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And when: " << desc )
#define CATCH_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc )
#define CATCH_AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc )
#elif defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) // ^^ prefixed, implemented | vv prefixed, disabled
#define CATCH_REQUIRE( ... ) (void)(0)
#define CATCH_REQUIRE_FALSE( ... ) (void)(0)
#define CATCH_REQUIRE_THROWS( ... ) (void)(0)
#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0)
#define CATCH_REQUIRE_NOTHROW( ... ) (void)(0)
#define CATCH_CHECK( ... ) (void)(0)
#define CATCH_CHECK_FALSE( ... ) (void)(0)
#define CATCH_CHECKED_IF( ... ) if (__VA_ARGS__)
#define CATCH_CHECKED_ELSE( ... ) if (!(__VA_ARGS__))
#define CATCH_CHECK_NOFAIL( ... ) (void)(0)
#define CATCH_CHECK_THROWS( ... ) (void)(0)
#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) (void)(0)
#define CATCH_CHECK_NOTHROW( ... ) (void)(0)
#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ))
#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ))
#define CATCH_METHOD_AS_TEST_CASE( method, ... )
#define CATCH_REGISTER_TEST_CASE( Function, ... ) (void)(0)
#define CATCH_SECTION( ... )
#define CATCH_DYNAMIC_SECTION( ... )
#define CATCH_FAIL( ... ) (void)(0)
#define CATCH_FAIL_CHECK( ... ) (void)(0)
#define CATCH_SUCCEED( ... ) (void)(0)
#define CATCH_SKIP( ... ) (void)(0)
#define CATCH_STATIC_REQUIRE( ... ) (void)(0)
#define CATCH_STATIC_REQUIRE_FALSE( ... ) (void)(0)
#define CATCH_STATIC_CHECK( ... ) (void)(0)
#define CATCH_STATIC_CHECK_FALSE( ... ) (void)(0)
// "BDD-style" convenience wrappers
#define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ))
#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), className )
#define CATCH_GIVEN( desc )
#define CATCH_AND_GIVEN( desc )
#define CATCH_WHEN( desc )
#define CATCH_AND_WHEN( desc )
#define CATCH_THEN( desc )
#define CATCH_AND_THEN( desc )
#elif !defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE) // ^^ prefixed, disabled | vv unprefixed, implemented
#define REQUIRE( ... ) INTERNAL_CATCH_TEST( "REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ )
#define REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ )
#define REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__ )
#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr )
#define REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ )
#define CHECK( ... ) INTERNAL_CATCH_TEST( "CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
#define CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ )
#define CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ )
#define CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ )
#define CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ )
#define CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr )
#define CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ )
#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ )
#define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )
#define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ )
#define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ )
#define DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ )
#define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ )
#define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
#define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )
#define SKIP( ... ) INTERNAL_CATCH_MSG( "SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ )
#if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE)
#define STATIC_REQUIRE( ... ) static_assert( __VA_ARGS__, #__VA_ARGS__ ); SUCCEED( #__VA_ARGS__ )
#define STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); SUCCEED( "!(" #__VA_ARGS__ ")" )
#define STATIC_CHECK( ... ) static_assert( __VA_ARGS__, #__VA_ARGS__ ); SUCCEED( #__VA_ARGS__ )
#define STATIC_CHECK_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); SUCCEED( "!(" #__VA_ARGS__ ")" )
#else
#define STATIC_REQUIRE( ... ) REQUIRE( __VA_ARGS__ )
#define STATIC_REQUIRE_FALSE( ... ) REQUIRE_FALSE( __VA_ARGS__ )
#define STATIC_CHECK( ... ) CHECK( __VA_ARGS__ )
#define STATIC_CHECK_FALSE( ... ) CHECK_FALSE( __VA_ARGS__ )
#endif
// "BDD-style" convenience wrappers
#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ )
#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ )
#define GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc )
#define AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And given: " << desc )
#define WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc )
#define AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And when: " << desc )
#define THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc )
#define AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc )
#elif !defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) // ^^ unprefixed, implemented | vv unprefixed, disabled
#define REQUIRE( ... ) (void)(0)
#define REQUIRE_FALSE( ... ) (void)(0)
#define REQUIRE_THROWS( ... ) (void)(0)
#define REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0)
#define REQUIRE_NOTHROW( ... ) (void)(0)
#define CHECK( ... ) (void)(0)
#define CHECK_FALSE( ... ) (void)(0)
#define CHECKED_IF( ... ) if (__VA_ARGS__)
#define CHECKED_ELSE( ... ) if (!(__VA_ARGS__))
#define CHECK_NOFAIL( ... ) (void)(0)
#define CHECK_THROWS( ... ) (void)(0)
#define CHECK_THROWS_AS( expr, exceptionType ) (void)(0)
#define CHECK_NOTHROW( ... ) (void)(0)
#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), __VA_ARGS__)
#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ))
#define METHOD_AS_TEST_CASE( method, ... )
#define REGISTER_TEST_CASE( Function, ... ) (void)(0)
#define SECTION( ... )
#define DYNAMIC_SECTION( ... )
#define FAIL( ... ) (void)(0)
#define FAIL_CHECK( ... ) (void)(0)
#define SUCCEED( ... ) (void)(0)
#define SKIP( ... ) (void)(0)
#define STATIC_REQUIRE( ... ) (void)(0)
#define STATIC_REQUIRE_FALSE( ... ) (void)(0)
#define STATIC_CHECK( ... ) (void)(0)
#define STATIC_CHECK_FALSE( ... ) (void)(0)
// "BDD-style" convenience wrappers
#define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ) )
#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), className )
#define GIVEN( desc )
#define AND_GIVEN( desc )
#define WHEN( desc )
#define AND_WHEN( desc )
#define THEN( desc )
#define AND_THEN( desc )
#endif // ^^ unprefixed, disabled
// end of user facing macros
#endif // CATCH_TEST_MACROS_HPP_INCLUDED

View File

@ -0,0 +1,141 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/catch_test_spec.hpp>
#include <catch2/interfaces/catch_interfaces_testcase.hpp>
#include <catch2/internal/catch_test_case_registry_impl.hpp>
#include <catch2/internal/catch_reusable_string_stream.hpp>
#include <catch2/internal/catch_string_manip.hpp>
#include <catch2/catch_test_case_info.hpp>
#include <algorithm>
#include <string>
#include <vector>
#include <ostream>
namespace Catch {
TestSpec::Pattern::Pattern( std::string const& name )
: m_name( name )
{}
TestSpec::Pattern::~Pattern() = default;
std::string const& TestSpec::Pattern::name() const {
return m_name;
}
TestSpec::NamePattern::NamePattern( std::string const& name, std::string const& filterString )
: Pattern( filterString )
, m_wildcardPattern( toLower( name ), CaseSensitive::No )
{}
bool TestSpec::NamePattern::matches( TestCaseInfo const& testCase ) const {
return m_wildcardPattern.matches( testCase.name );
}
void TestSpec::NamePattern::serializeTo( std::ostream& out ) const {
out << '"' << name() << '"';
}
TestSpec::TagPattern::TagPattern( std::string const& tag, std::string const& filterString )
: Pattern( filterString )
, m_tag( tag )
{}
bool TestSpec::TagPattern::matches( TestCaseInfo const& testCase ) const {
return std::find( begin( testCase.tags ),
end( testCase.tags ),
Tag( m_tag ) ) != end( testCase.tags );
}
void TestSpec::TagPattern::serializeTo( std::ostream& out ) const {
out << name();
}
bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const {
bool should_use = !testCase.isHidden();
for (auto const& pattern : m_required) {
should_use = true;
if (!pattern->matches(testCase)) {
return false;
}
}
for (auto const& pattern : m_forbidden) {
if (pattern->matches(testCase)) {
return false;
}
}
return should_use;
}
void TestSpec::Filter::serializeTo( std::ostream& out ) const {
bool first = true;
for ( auto const& pattern : m_required ) {
if ( !first ) {
out << ' ';
}
out << *pattern;
first = false;
}
for ( auto const& pattern : m_forbidden ) {
if ( !first ) {
out << ' ';
}
out << *pattern;
first = false;
}
}
std::string TestSpec::extractFilterName( Filter const& filter ) {
Catch::ReusableStringStream sstr;
sstr << filter;
return sstr.str();
}
bool TestSpec::hasFilters() const {
return !m_filters.empty();
}
bool TestSpec::matches( TestCaseInfo const& testCase ) const {
return std::any_of( m_filters.begin(), m_filters.end(), [&]( Filter const& f ){ return f.matches( testCase ); } );
}
TestSpec::Matches TestSpec::matchesByFilter( std::vector<TestCaseHandle> const& testCases, IConfig const& config ) const {
Matches matches;
matches.reserve( m_filters.size() );
for ( auto const& filter : m_filters ) {
std::vector<TestCaseHandle const*> currentMatches;
for ( auto const& test : testCases )
if ( isThrowSafe( test, config ) &&
filter.matches( test.getTestCaseInfo() ) )
currentMatches.emplace_back( &test );
matches.push_back(
FilterMatch{ extractFilterName( filter ), currentMatches } );
}
return matches;
}
const TestSpec::vectorStrings& TestSpec::getInvalidSpecs() const {
return m_invalidSpecs;
}
void TestSpec::serializeTo( std::ostream& out ) const {
bool first = true;
for ( auto const& filter : m_filters ) {
if ( !first ) {
out << ',';
}
out << filter;
first = false;
}
}
}

View File

@ -0,0 +1,119 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#ifndef CATCH_TEST_SPEC_HPP_INCLUDED
#define CATCH_TEST_SPEC_HPP_INCLUDED
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpadded"
#endif
#include <catch2/internal/catch_unique_ptr.hpp>
#include <catch2/internal/catch_wildcard_pattern.hpp>
#include <iosfwd>
#include <string>
#include <vector>
namespace Catch {
class IConfig;
struct TestCaseInfo;
class TestCaseHandle;
class TestSpec {
class Pattern {
public:
explicit Pattern( std::string const& name );
virtual ~Pattern();
virtual bool matches( TestCaseInfo const& testCase ) const = 0;
std::string const& name() const;
private:
virtual void serializeTo( std::ostream& out ) const = 0;
// Writes string that would be reparsed into the pattern
friend std::ostream& operator<<(std::ostream& out,
Pattern const& pattern) {
pattern.serializeTo( out );
return out;
}
std::string const m_name;
};
class NamePattern : public Pattern {
public:
explicit NamePattern( std::string const& name, std::string const& filterString );
bool matches( TestCaseInfo const& testCase ) const override;
private:
void serializeTo( std::ostream& out ) const override;
WildcardPattern m_wildcardPattern;
};
class TagPattern : public Pattern {
public:
explicit TagPattern( std::string const& tag, std::string const& filterString );
bool matches( TestCaseInfo const& testCase ) const override;
private:
void serializeTo( std::ostream& out ) const override;
std::string m_tag;
};
struct Filter {
std::vector<Detail::unique_ptr<Pattern>> m_required;
std::vector<Detail::unique_ptr<Pattern>> m_forbidden;
//! Serializes this filter into a string that would be parsed into
//! an equivalent filter
void serializeTo( std::ostream& out ) const;
friend std::ostream& operator<<(std::ostream& out, Filter const& f) {
f.serializeTo( out );
return out;
}
bool matches( TestCaseInfo const& testCase ) const;
};
static std::string extractFilterName( Filter const& filter );
public:
struct FilterMatch {
std::string name;
std::vector<TestCaseHandle const*> tests;
};
using Matches = std::vector<FilterMatch>;
using vectorStrings = std::vector<std::string>;
bool hasFilters() const;
bool matches( TestCaseInfo const& testCase ) const;
Matches matchesByFilter( std::vector<TestCaseHandle> const& testCases, IConfig const& config ) const;
const vectorStrings & getInvalidSpecs() const;
private:
std::vector<Filter> m_filters;
std::vector<std::string> m_invalidSpecs;
friend class TestSpecParser;
//! Serializes this test spec into a string that would be parsed into
//! equivalent test spec
void serializeTo( std::ostream& out ) const;
friend std::ostream& operator<<(std::ostream& out,
TestSpec const& spec) {
spec.serializeTo( out );
return out;
}
};
}
#ifdef __clang__
#pragma clang diagnostic pop
#endif
#endif // CATCH_TEST_SPEC_HPP_INCLUDED

Some files were not shown because too many files have changed in this diff Show More