chore: update to 2a0400ade5535001bfc09e0e7531515e232be6e7

This commit is contained in:
2024-07-03 19:55:17 -05:00
parent e009f01d38
commit c461e29eaa
91 changed files with 39733 additions and 3468 deletions

View File

@@ -0,0 +1,57 @@
Dear ImGui Test Engine License (v1.03)
Copyright (c) 2018-2023 Omar Cornut
This document is a legal agreement ("License") between you ("Licensee") and
DISCO HELLO ("Licensor") that governs your use of Dear ImGui Test Engine ("Software").
1. LICENSE MODELS
1.1. Free license
The Licensor grants you a free license ("Free License") if you meet ANY of the following
criterion:
- You are a natural person;
- You are not a legal entity, or you are a not-for-profit legal entity;
- You are using this Software for educational purposes;
- You are using this Software to create Derivative Software released publicly and under
an Open Source license, as defined by the Open Source Initiative;
- You are a legal entity with a turnover inferior to 2 million USD (or equivalent) during
your last fiscal year.
1.2. Paid license
If you do not meet any criterion of Article 1.1, Licensor grants you a trial period of a
maximum of 45 days, at no charge. Following this trial period, you must subscribe to a paid
license ("Paid License") with the Licensor to continue using the Software.
Paid Licenses are exclusively sold by DISCO HELLO. Paid Licenses and the associated
information are available at the following URL: http://www.dearimgui.com/licenses
2. GRANT OF LICENSE
2.1. License scope
A limited and non-exclusive license is hereby granted, to the Licensee, to reproduce,
execute, publicly perform, and display, use, copy, modify, merge, distribute, or create
derivative works based on and/or derived from the Software ("Derivative Software").
2.2. Right of distribution
License holders may also publish and/or distribute the Software or any Derivative
Software. The above copyright notice and this license shall be included in all copies
or substantial portions of the Software and/or Derivative Software.
License holders may add their own attribution notices within the Derivative Software
that they distribute. Such attribution notices must not directly or indirectly imply a
modification of the License. License holders may provide to their modifications their
own copyright and/or additional or different terms and conditions, providing such
conditions complies with this License.
3. DISCLAIMER
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,180 @@
// dear imgui test engine
// (screen/video capture tool)
// This is usable as a standalone applet or controlled by the test engine.
#pragma once
#include "imgui_te_utils.h" // ImFuncPtr
//-----------------------------------------------------------------------------
// Forward declarations
//-----------------------------------------------------------------------------
// Our types
struct ImGuiCaptureArgs; // Parameters for Capture
struct ImGuiCaptureContext; // State of an active capture tool
struct ImGuiCaptureImageBuf; // Simple helper to store an RGBA image in memory
struct ImGuiCaptureToolUI; // Capture tool instance + UI window
typedef unsigned int ImGuiCaptureFlags; // See enum: ImGuiCaptureFlags_
// Capture function which needs to be provided by user application
typedef bool (ImGuiScreenCaptureFunc)(ImGuiID viewport_id, int x, int y, int w, int h, unsigned int* pixels, void* user_data);
// External types
struct ImGuiWindow; // imgui.h
//-----------------------------------------------------------------------------
// [Internal]
// Helper class for simple bitmap manipulation (not particularly efficient!)
struct IMGUI_API ImGuiCaptureImageBuf
{
int Width;
int Height;
unsigned int* Data; // RGBA8
ImGuiCaptureImageBuf() { Width = Height = 0; Data = NULL; }
~ImGuiCaptureImageBuf() { Clear(); }
void Clear(); // Free allocated memory buffer if such exists.
void CreateEmpty(int w, int h); // Reallocate buffer for pixel data and zero it.
bool SaveFile(const char* filename); // Save pixel data to specified image file.
void RemoveAlpha(); // Clear alpha channel from all pixels.
};
enum ImGuiCaptureFlags_ : unsigned int
{
ImGuiCaptureFlags_None = 0,
ImGuiCaptureFlags_StitchAll = 1 << 0, // Capture entire window scroll area (by scrolling and taking multiple screenshot). Only works for a single window.
ImGuiCaptureFlags_IncludeOtherWindows = 1 << 1, // Disable hiding other windows (when CaptureAddWindow has been called by default other windows are hidden)
ImGuiCaptureFlags_IncludeTooltipsAndPopups = 1 << 2, // Expand capture area to automatically include visible popups and tooltips (use with ImGuiCaptureflags_HideOtherWindows)
ImGuiCaptureFlags_HideMouseCursor = 1 << 3, // Hide render software mouse cursor during capture.
ImGuiCaptureFlags_Instant = 1 << 4, // Perform capture on very same frame. Only works when capturing a rectangular region. Unsupported features: content stitching, window hiding, window relocation.
ImGuiCaptureFlags_NoSave = 1 << 5 // Do not save output image.
};
// Defines input and output arguments for capture process.
// When capturing from tests you can usually use the ImGuiTestContext::CaptureXXX() helpers functions.
struct ImGuiCaptureArgs
{
// [Input]
ImGuiCaptureFlags InFlags = 0; // Flags for customizing behavior of screenshot tool.
ImVector<ImGuiWindow*> InCaptureWindows; // Windows to capture. All other windows will be hidden. May be used with InCaptureRect to capture only some windows in specified rect.
ImRect InCaptureRect; // Screen rect to capture. Does not include padding.
float InPadding = 16.0f; // Extra padding at the edges of the screenshot. Ensure that there is available space around capture rect horizontally, also vertically if ImGuiCaptureFlags_StitchFullContents is not used.
char InOutputFile[256] = ""; // Output will be saved to a file if InOutputImageBuf is NULL.
ImGuiCaptureImageBuf* InOutputImageBuf = NULL; // _OR_ Output will be saved to image buffer if specified.
int InRecordFPSTarget = 30; // FPS target for recording videos.
int InSizeAlign = 0; // Resolution alignment (0 = auto, 1 = no alignment, >= 2 = align width/height to be multiple of given value)
// [Output]
ImVec2 OutImageSize; // Produced image size.
};
enum ImGuiCaptureStatus
{
ImGuiCaptureStatus_InProgress,
ImGuiCaptureStatus_Done,
ImGuiCaptureStatus_Error
};
struct ImGuiCaptureWindowData
{
ImGuiWindow* Window;
ImRect BackupRect;
ImVec2 PosDuringCapture;
};
// Implements functionality for capturing images
struct IMGUI_API ImGuiCaptureContext
{
// IO
ImFuncPtr(ImGuiScreenCaptureFunc) ScreenCaptureFunc = NULL; // Graphics backend specific function that captures specified portion of framebuffer and writes RGBA data to `pixels` buffer.
void* ScreenCaptureUserData = NULL; // Custom user pointer which is passed to ScreenCaptureFunc. (Optional)
char* VideoCaptureEncoderPath = NULL; // Video encoder path (not owned, stored externally).
int VideoCaptureEncoderPathSize = 0; // Optional. Set in order to edit this parameter from UI.
char* VideoCaptureEncoderParams = NULL; // Video encoder params (not owned, stored externally).
int VideoCaptureEncoderParamsSize = 0; // Optional. Set in order to edit this parameter from UI.
char* GifCaptureEncoderParams = NULL; // Video encoder params for GIF output (not owned, stored externally).
int GifCaptureEncoderParamsSize = 0; // Optional. Set in order to edit this parameter from UI.
// [Internal]
ImRect _CaptureRect; // Viewport rect that is being captured.
ImRect _CapturedWindowRect; // Top-left corner of region that covers all windows included in capture. This is not same as _CaptureRect.Min when capturing explicitly specified rect.
int _ChunkNo = 0; // Number of chunk that is being captured when capture spans multiple frames.
int _FrameNo = 0; // Frame number during capture process that spans multiple frames.
ImVec2 _MouseRelativeToWindowPos; // Mouse cursor position relative to captured window (when _StitchAll is in use).
ImGuiWindow* _HoveredWindow = NULL; // Window which was hovered at capture start.
ImGuiCaptureImageBuf _CaptureBuf; // Output image buffer.
const ImGuiCaptureArgs* _CaptureArgs = NULL; // Current capture args. Set only if capture is in progress.
ImVector<ImGuiCaptureWindowData> _WindowsData; // Backup windows that will have their rect modified and restored. args->InCaptureWindows can not be used because popups may get closed during capture and no longer appear in that list.
// [Internal] Video recording
bool _VideoRecording = false; // Flag indicating that video recording is in progress.
double _VideoLastFrameTime = 0; // Time when last video frame was recorded.
FILE* _VideoEncoderPipe = NULL; // File writing to stdin of video encoder process.
// [Internal] Backups
bool _BackupMouseDrawCursor = false; // Initial value of g.IO.MouseDrawCursor
ImVec2 _BackupDisplayWindowPadding; // Backup padding. We set it to {0, 0} during capture.
ImVec2 _BackupDisplaySafeAreaPadding; // Backup padding. We set it to {0, 0} during capture.
//-------------------------------------------------------------------------
// Functions
//-------------------------------------------------------------------------
ImGuiCaptureContext(ImGuiScreenCaptureFunc capture_func = NULL) { ScreenCaptureFunc = capture_func; _MouseRelativeToWindowPos = ImVec2(-FLT_MAX, -FLT_MAX); }
// These functions should be called from appropriate context hooks. See ImGui::AddContextHook() for more info.
// (ImGuiTestEngine automatically calls that for you, so this only apply to independently created instance)
void PreNewFrame();
void PreRender();
void PostRender();
// Update capturing. If this function returns true then it should be called again with same arguments on the next frame.
ImGuiCaptureStatus CaptureUpdate(ImGuiCaptureArgs* args);
void RestoreBackedUpData();
void ClearState();
// Begin video capture. Call CaptureUpdate() every frame afterwards until it returns false.
void BeginVideoCapture(ImGuiCaptureArgs* args);
void EndVideoCapture();
bool IsCapturingVideo();
bool IsCapturing();
};
//-----------------------------------------------------------------------------
// ImGuiCaptureToolUI
//-----------------------------------------------------------------------------
// Implements UI for capturing images
// (when using ImGuiTestEngine scripting API you may not need to use this at all)
struct IMGUI_API ImGuiCaptureToolUI
{
float SnapGridSize = 32.0f; // Size of the grid cell for "snap to grid" functionality.
char OutputLastFilename[256] = ""; // File name of last captured file.
char* VideoCaptureExtension = NULL; // Video file extension (e.g. ".gif" or ".mp4")
int VideoCaptureExtensionSize = 0; // Optional. Set in order to edit this parameter from UI.
ImGuiCaptureArgs _CaptureArgs; // Capture args
bool _StateIsPickingWindow = false;
bool _StateIsCapturing = false;
ImVector<ImGuiID> _SelectedWindows;
char _OutputFileTemplate[256] = ""; //
int _FileCounter = 0; // Counter which may be appended to file name when saving. By default, counting starts from 1. When done this field holds number of saved files.
// Public
ImGuiCaptureToolUI();
void ShowCaptureToolWindow(ImGuiCaptureContext* context, bool* p_open = NULL); // Render a capture tool window with various options and utilities.
// [Internal]
void _CaptureWindowPicker(ImGuiCaptureArgs* args); // Render a window picker that captures picked window to file specified in file_name.
void _CaptureWindowsSelector(ImGuiCaptureContext* context, ImGuiCaptureArgs* args); // Render a selector for selecting multiple windows for capture.
void _SnapWindowsToGrid(float cell_size); // Snap edges of all visible windows to a virtual grid.
bool _InitializeOutputFile(); // Format output file template into capture args struct and ensure target directory exists.
bool _ShowEncoderConfigFields(ImGuiCaptureContext* context);
};
#define IMGUI_CAPTURE_DEFAULT_VIDEO_PARAMS_FOR_FFMPEG "-hide_banner -loglevel error -r $FPS -f rawvideo -pix_fmt rgba -s $WIDTHx$HEIGHT -i - -threads 0 -y -preset ultrafast -pix_fmt yuv420p -crf 20 $OUTPUT"
#define IMGUI_CAPTURE_DEFAULT_GIF_PARAMS_FOR_FFMPEG "-hide_banner -loglevel error -r $FPS -f rawvideo -pix_fmt rgba -s $WIDTHx$HEIGHT -i - -threads 0 -y -filter_complex \"split=2 [a] [b]; [a] palettegen [pal]; [b] [pal] paletteuse\" $OUTPUT"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,616 @@
// dear imgui test engine
// (context when a running test + end user automation API)
// This is the main (if not only) interface that your Tests will be using.
#pragma once
#include "imgui.h"
#include "imgui_internal.h" // ImGuiAxis, ImGuiItemStatusFlags, ImGuiInputSource, ImGuiWindow
#include "imgui_te_engine.h" // ImGuiTestStatus, ImGuiTestRunFlags, ImGuiTestActiveFunc, ImGuiTestItemInfo, ImGuiTestLogFlags
/*
Index of this file:
// [SECTION] Header mess, warnings
// [SECTION] Forward declarations
// [SECTION] ImGuiTestRef
// [SECTION] Helper keys
// [SECTION] ImGuiTestContext related Flags/Enumerations
// [SECTION] ImGuiTestGenericVars, ImGuiTestGenericItemStatus
// [SECTION] ImGuiTestContext
// [SECTION] Debugging macros: IM_SUSPEND_TESTFUNC()
// [SECTION] Testing/Checking macros: IM_CHECK(), IM_ERRORF() etc.
*/
//-------------------------------------------------------------------------
// [SECTION] Header mess, warnings
//-------------------------------------------------------------------------
// Undo some of the damage done by <windows.h>
#ifdef Yield
#undef Yield
#endif
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx'
#elif defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind
#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead
#endif
//-------------------------------------------------------------------------
// [SECTION] Forward declarations
//-------------------------------------------------------------------------
// This file
typedef int ImGuiTestOpFlags; // Flags: See ImGuiTestOpFlags_
// External: imgui
struct ImGuiDockNode;
struct ImGuiTabBar;
struct ImGuiWindow;
// External: test engine
struct ImGuiTest; // A test registered with IM_REGISTER_TEST()
struct ImGuiTestEngine; // Test Engine Instance (opaque)
struct ImGuiTestEngineIO; // Test Engine IO structure (configuration flags, state)
struct ImGuiTestItemInfo; // Information gathered about an item: label, status, bounding box etc.
struct ImGuiTestItemList; // Result of an GatherItems() query
struct ImGuiTestInputs; // Test Engine Simulated Inputs structure (opaque)
struct ImGuiTestGatherTask; // Test Engine task for scanning/finding items
struct ImGuiCaptureArgs; // Parameters for ctx->CaptureXXX functions
enum ImGuiTestVerboseLevel : int;
//-------------------------------------------------------------------------
// [SECTION] ImGuiTestRef
//-------------------------------------------------------------------------
// Weak reference to an Item/Window given an hashed ID _or_ a string path ID.
// This is most often passed as argument to function and generally has a very short lifetime.
// Documentation: https://github.com/ocornut/imgui_test_engine/wiki/Named-References
// (SUGGESTION: add those constructors to "VA Step Filter" (Visual Assist) or a .natstepfilter file (Visual Studio) so they are skipped by F11 (StepInto)
struct IMGUI_API ImGuiTestRef
{
ImGuiID ID; // Pre-hashed ID
const char* Path; // Relative or absolute path (string pointed to, not owned, as our lifetime is very short)
ImGuiTestRef() { ID = 0; Path = NULL; }
ImGuiTestRef(ImGuiID id) { ID = id; Path = NULL; }
ImGuiTestRef(const char* path) { ID = 0; Path = path; }
bool IsEmpty() const { return ID == 0 && (Path == NULL || Path[0] == 0); }
};
// Debug helper to output a string showing the Path, ID or Debug Label based on what is available (some items only have ID as we couldn't find/store a Path)
// (The size is arbitrary, this is only used for logging info the user/debugger)
struct IMGUI_API ImGuiTestRefDesc
{
char Buf[80];
const char* c_str() { return Buf; }
ImGuiTestRefDesc(const ImGuiTestRef& ref, const ImGuiTestItemInfo* item);
};
//-------------------------------------------------------------------------
// [SECTION] ImGuiTestContext related Flags/Enumerations
//-------------------------------------------------------------------------
// Named actions. Generally you will call the named helpers e.g. ItemClick(). This is used by shared/low-level functions such as ItemAction().
enum ImGuiTestAction
{
ImGuiTestAction_Unknown = 0,
ImGuiTestAction_Hover, // Move mouse
ImGuiTestAction_Click, // Move mouse and click
ImGuiTestAction_DoubleClick, // Move mouse and double-click
ImGuiTestAction_Check, // Check item if unchecked (Checkbox, MenuItem or any widget reporting ImGuiItemStatusFlags_Checkable)
ImGuiTestAction_Uncheck, // Uncheck item if checked
ImGuiTestAction_Open, // Open item if closed (TreeNode, BeginMenu or any widget reporting ImGuiItemStatusFlags_Openable)
ImGuiTestAction_Close, // Close item if opened
ImGuiTestAction_Input, // Start text inputing into a field (e.g. CTRL+Click on Drags/Slider, click on InputText etc.)
ImGuiTestAction_NavActivate, // Activate item with navigation
ImGuiTestAction_COUNT
};
// Generic flags for many ImGuiTestContext functions
enum ImGuiTestOpFlags_
{
ImGuiTestOpFlags_None = 0,
ImGuiTestOpFlags_NoCheckHoveredId = 1 << 1, // Don't check for HoveredId after aiming for a widget. A few situations may want this: while e.g. dragging or another items prevents hovering, or for items that don't use ItemHoverable()
ImGuiTestOpFlags_NoError = 1 << 2, // Don't abort/error e.g. if the item cannot be found or the operation doesn't succeed.
ImGuiTestOpFlags_NoFocusWindow = 1 << 3, // Don't focus window when aiming at an item
ImGuiTestOpFlags_NoAutoUncollapse = 1 << 4, // Disable automatically uncollapsing windows (useful when specifically testing Collapsing behaviors)
ImGuiTestOpFlags_NoAutoOpenFullPath = 1 << 5, // Disable automatically opening intermediaries (e.g. ItemClick("Hello/OK") will automatically first open "Hello" if "OK" isn't found. Only works if ref is a string path.
ImGuiTestOpFlags_IsSecondAttempt = 1 << 6, // Used by recursing functions to indicate a second attempt
ImGuiTestOpFlags_MoveToEdgeL = 1 << 7, // Simple Dumb aiming helpers to test widget that care about clicking position. May need to replace will better functionalities.
ImGuiTestOpFlags_MoveToEdgeR = 1 << 8,
ImGuiTestOpFlags_MoveToEdgeU = 1 << 9,
ImGuiTestOpFlags_MoveToEdgeD = 1 << 10,
};
// Advanced filtering for ItemActionAll()
struct IMGUI_API ImGuiTestActionFilter
{
int MaxDepth;
int MaxPasses;
const int* MaxItemCountPerDepth;
ImGuiItemStatusFlags RequireAllStatusFlags;
ImGuiItemStatusFlags RequireAnyStatusFlags;
ImGuiTestActionFilter() { MaxDepth = -1; MaxPasses = -1; MaxItemCountPerDepth = NULL; RequireAllStatusFlags = RequireAnyStatusFlags = 0; }
};
//-------------------------------------------------------------------------
// [SECTION] ImGuiTestGenericVars, ImGuiTestGenericItemStatus
//-------------------------------------------------------------------------
// Helper struct to store various query-able state of an item.
// This facilitate interactions between GuiFunc and TestFunc, since those state are frequently used.
struct IMGUI_API ImGuiTestGenericItemStatus
{
int RetValue; // return value
int Hovered; // result of IsItemHovered()
int Active; // result of IsItemActive()
int Focused; // result of IsItemFocused()
int Clicked; // result of IsItemClicked()
int Visible; // result of IsItemVisible()
int Edited; // result of IsItemEdited()
int Activated; // result of IsItemActivated()
int Deactivated; // result of IsItemDeactivated()
int DeactivatedAfterEdit; // result of IsItemDeactivatedAfterEdit()
ImGuiTestGenericItemStatus() { Clear(); }
void Clear() { memset(this, 0, sizeof(*this)); }
void QuerySet(bool ret_val = false) { Clear(); QueryInc(ret_val); }
void QueryInc(bool ret_val = false) { RetValue += ret_val; Hovered += ImGui::IsItemHovered(); Active += ImGui::IsItemActive(); Focused += ImGui::IsItemFocused(); Clicked += ImGui::IsItemClicked(); Visible += ImGui::IsItemVisible(); Edited += ImGui::IsItemEdited(); Activated += ImGui::IsItemActivated(); Deactivated += ImGui::IsItemDeactivated(); DeactivatedAfterEdit += ImGui::IsItemDeactivatedAfterEdit(); }
};
// Generic structure with various storage fields.
// This is useful for tests to quickly share data between GuiFunc and TestFunc without creating custom data structure.
// If those fields are not enough: using test->SetVarsDataType<>() + ctx->GetVars<>() it is possible to store custom data.
struct IMGUI_API ImGuiTestGenericVars
{
// Generic storage with a bit of semantic to make user/test code look neater
int Step;
int Count;
ImGuiID DockId;
ImGuiID OwnerId;
ImGuiWindowFlags WindowFlags;
ImGuiTableFlags TableFlags;
ImGuiPopupFlags PopupFlags;
ImGuiTestGenericItemStatus Status;
bool ShowWindow1, ShowWindow2;
bool UseClipper;
bool UseViewports;
float Width;
ImVec2 Pos;
ImVec2 Size;
ImVec2 Pivot;
ImVec4 Color1, Color2;
// Generic unnamed storage
int Int1, Int2, IntArray[10];
float Float1, Float2, FloatArray[10];
bool Bool1, Bool2, BoolArray[10];
ImGuiID Id, IdArray[10];
char Str1[256], Str2[256];
ImGuiTestGenericVars() { Clear(); }
void Clear() { memset(this, 0, sizeof(*this)); }
};
//-------------------------------------------------------------------------
// [SECTION] ImGuiTestContext
// Context for a running ImGuiTest
// This is the interface that most tests will interact with.
//-------------------------------------------------------------------------
struct IMGUI_API ImGuiTestContext
{
// User variables
ImGuiTestGenericVars GenericVars; // Generic variables holder for convenience.
void* UserVars = NULL; // Access using ctx->GetVars<Type>(). Setup with test->SetVarsDataType<>().
// Public fields
ImGuiContext* UiContext = NULL; // UI context
ImGuiTestEngineIO* EngineIO = NULL; // Test Engine IO/settings
ImGuiTest* Test = NULL; // Test currently running
ImGuiTestOutput* TestOutput = NULL; // Test output (generally == &Test->Output)
ImGuiTestOpFlags OpFlags = ImGuiTestOpFlags_None; // Flags affecting all operation (supported: ImGuiTestOpFlags_NoAutoUncollapse)
int PerfStressAmount = 0; // Convenience copy of engine->IO.PerfStressAmount
int FrameCount = 0; // Test frame count (restarts from zero every time)
int FirstTestFrameCount = 0; // First frame where TestFunc is running (after warm-up frame). This is generally -1 or 0 depending on whether we have warm up enabled
bool FirstGuiFrame = false;
bool HasDock = false; // #ifdef IMGUI_HAS_DOCK expressed in an easier to test value
ImGuiCaptureArgs* CaptureArgs = NULL; // Capture settings used by ctx->Capture*() functions
//-------------------------------------------------------------------------
// [Internal Fields]
//-------------------------------------------------------------------------
ImGuiTestEngine* Engine = NULL;
ImGuiTestInputs* Inputs = NULL;
ImGuiTestRunFlags RunFlags = ImGuiTestRunFlags_None;
ImGuiTestActiveFunc ActiveFunc = ImGuiTestActiveFunc_None; // None/GuiFunc/TestFunc
double RunningTime = 0.0; // Amount of wall clock time the Test has been running. Used by safety watchdog.
int ActionDepth = 0; // Nested depth of ctx-> function calls (used to decorate log)
int CaptureCounter = 0; // Number of captures
int ErrorCounter = 0; // Number of errors (generally this maxxes at 1 as most functions will early out)
bool Abort = false;
double PerfRefDt = -1.0;
int PerfIterations = 400; // Number of frames for PerfCapture() measurements
char RefStr[256] = { 0 }; // Reference window/path over which all named references are based
ImGuiID RefID = 0; // Reference ID over which all named references are based
ImGuiID RefWindowID = 0; // ID of a window that contains RefID item
ImGuiInputSource InputMode = ImGuiInputSource_Mouse; // Prefer interacting with mouse/keyboard/gamepad
ImVector<char> Clipboard; // Private clipboard for the test instance
ImVector<ImGuiWindow*> ForeignWindowsToHide;
ImGuiTestItemInfo DummyItemInfoNull; // Storage for ItemInfoNull()
bool CachedLinesPrintedToTTY = false;
//-------------------------------------------------------------------------
// Public API
//-------------------------------------------------------------------------
// Main control
void RecoverFromUiContextErrors();
void Finish(ImGuiTestStatus status = ImGuiTestStatus_Success); // Set test status and stop running. Usually called when running test logic from GuiFunc() only.
ImGuiTestStatus RunChildTest(const char* test_name, ImGuiTestRunFlags flags = 0); // [Experimental] Run another test from the current test.
template <typename T> T& GetVars() { IM_ASSERT(UserVars != NULL); return *(T*)(UserVars); }// Campanion to using t->SetVarsDataType<>(). FIXME: Assert to compare sizes
// Main status queries
bool IsError() const { return TestOutput->Status == ImGuiTestStatus_Error || Abort; }
bool IsWarmUpGuiFrame() const { return FrameCount < FirstTestFrameCount; } // Unless test->Flags has ImGuiTestFlags_NoGuiWarmUp, we run GuiFunc() twice before running TestFunc(). Those frames are called "WarmUp" frames.
bool IsFirstGuiFrame() const { return FirstGuiFrame; }
bool IsFirstTestFrame() const { return FrameCount == FirstTestFrameCount; } // First frame where TestFunc is running (after warm-up frame).
bool IsGuiFuncOnly() const { return (RunFlags & ImGuiTestRunFlags_GuiFuncOnly) != 0; }
// Debugging
bool SuspendTestFunc(const char* file = NULL, int line = 0); // [DEBUG] Generally called via IM_SUSPEND_TESTFUNC
// Logging
void LogEx(ImGuiTestVerboseLevel level, ImGuiTestLogFlags flags, const char* fmt, ...) IM_FMTARGS(4);
void LogExV(ImGuiTestVerboseLevel level, ImGuiTestLogFlags flags, const char* fmt, va_list args) IM_FMTLIST(4);
void LogToTTY(ImGuiTestVerboseLevel level, const char* message, const char* message_end = NULL);
void LogToDebugger(ImGuiTestVerboseLevel level, const char* message);
void LogDebug(const char* fmt, ...) IM_FMTARGS(2); // ImGuiTestVerboseLevel_Debug or ImGuiTestVerboseLevel_Trace depending on context depth
void LogInfo(const char* fmt, ...) IM_FMTARGS(2); // ImGuiTestVerboseLevel_Info
void LogWarning(const char* fmt, ...) IM_FMTARGS(2); // ImGuiTestVerboseLevel_Warning
void LogError(const char* fmt, ...) IM_FMTARGS(2); // ImGuiTestVerboseLevel_Error
void LogBasicUiState();
void LogItemList(ImGuiTestItemList* list);
// Yield, Timing
void Yield(int count = 1);
void YieldUntil(int frame_count);
void Sleep(float time_in_second); // Sleep for a given simulation time, unless in Fast mode
void SleepShort(); // Standard short delay of io.ActionDelayShort (~0.15f), unless in Fast mode.
void SleepStandard(); // Standard regular delay of io.ActionDelayStandard (~0.40f), unless in Fast mode.
void SleepNoSkip(float time_in_second, float framestep_in_second);
// Base Reference
// - ItemClick("Window/Button") --> click "Window/Button"
// - SetRef("Window"), ItemClick("Button") --> click "Window/Button"
// - SetRef("Window"), ItemClick("/Button") --> click "Window/Button"
// - SetRef("Window"), ItemClick("//Button") --> click "/Button"
// - SetRef("//$FOCUSED"), ItemClick("Button") --> click "Button" in focused window.
// See https://github.com/ocornut/imgui_test_engine/wiki/Named-References about using ImGuiTestRef in all ImGuiTestContext functions.
// Note: SetRef() may take multiple frames to complete if specified ref is an item id.
void SetRef(ImGuiTestRef ref);
void SetRef(ImGuiWindow* window); // Shortcut to SetRef(window->Name) which works for ChildWindow (see code)
ImGuiTestRef GetRef();
// Windows
ImGuiTestItemInfo* WindowInfo(ImGuiTestRef window_ref, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None);
void WindowClose(ImGuiTestRef window_ref);
void WindowCollapse(ImGuiTestRef window_ref, bool collapsed);
void WindowFocus(ImGuiTestRef window_ref, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None);
void WindowBringToFront(ImGuiTestRef window_ref, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None);
void WindowMove(ImGuiTestRef window_ref, ImVec2 pos, ImVec2 pivot = ImVec2(0.0f, 0.0f), ImGuiTestOpFlags flags = ImGuiTestOpFlags_None);
void WindowResize(ImGuiTestRef window_ref, ImVec2 sz);
bool WindowTeleportToMakePosVisible(ImGuiTestRef window_ref, ImVec2 pos_in_window);
ImGuiWindow*GetWindowByRef(ImGuiTestRef window_ref);
// Popups
void PopupCloseOne();
void PopupCloseAll();
ImGuiID PopupGetWindowID(ImGuiTestRef ref);
// Get hash for a decorated ID Path.
// Note: for windows you may use WindowInfo()
ImGuiID GetID(ImGuiTestRef ref);
ImGuiID GetID(ImGuiTestRef ref, ImGuiTestRef seed_ref);
// Miscellaneous helpers
ImVec2 GetPosOnVoid(ImGuiViewport* viewport); // Find a point that has no windows // FIXME: This needs error return and flag to enable/disable forcefully finding void.
ImVec2 GetWindowTitlebarPoint(ImGuiTestRef window_ref); // Return a clickable point on window title-bar (window tab for docked windows).
ImVec2 GetMainMonitorWorkPos(); // Work pos and size of main viewport when viewports are disabled, or work pos and size of monitor containing main viewport when viewports are enabled.
ImVec2 GetMainMonitorWorkSize();
// Screenshot/Video Captures
void CaptureReset(); // Reset state (use when doing multiple captures)
void CaptureSetExtension(const char* ext); // Set capture file format (otherwise for video this default to EngineIO->VideoCaptureExtension)
bool CaptureAddWindow(ImGuiTestRef ref); // Add window to be captured (default to capture everything)
void CaptureScreenshotWindow(ImGuiTestRef ref, int capture_flags = 0); // Trigger a screen capture of a single window (== CaptureAddWindow() + CaptureScreenshot())
bool CaptureScreenshot(int capture_flags = 0); // Trigger a screen capture
bool CaptureBeginVideo(); // Start a video capture
bool CaptureEndVideo();
// Mouse inputs
void MouseMove(ImGuiTestRef ref, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None);
void MouseMoveToPos(ImVec2 pos);
void MouseTeleportToPos(ImVec2 pos);
void MouseClick(ImGuiMouseButton button = 0);
void MouseClickMulti(ImGuiMouseButton button, int count);
void MouseDoubleClick(ImGuiMouseButton button = 0);
void MouseDown(ImGuiMouseButton button = 0);
void MouseUp(ImGuiMouseButton button = 0);
void MouseLiftDragThreshold(ImGuiMouseButton button = 0);
void MouseDragWithDelta(ImVec2 delta, ImGuiMouseButton button = 0);
void MouseWheel(ImVec2 delta);
void MouseWheelX(float dx) { MouseWheel(ImVec2(dx, 0.0f)); }
void MouseWheelY(float dy) { MouseWheel(ImVec2(0.0f, dy)); }
void MouseMoveToVoid(ImGuiViewport* viewport = NULL);
void MouseClickOnVoid(ImGuiMouseButton button = 0, ImGuiViewport* viewport = NULL);
ImGuiWindow*FindHoveredWindowAtPos(const ImVec2& pos);
bool FindExistingVoidPosOnViewport(ImGuiViewport* viewport, ImVec2* out);
// Mouse inputs: Viewports
// - This is automatically called by SetRef() and any mouse action taking an item reference (e.g. ItemClick("button"), MouseClick("button"))
// - But when using raw position directy e.g. MouseMoveToPos() / MouseTeleportToPos() without referring to the parent window before, this needs to be set.
void MouseSetViewport(ImGuiWindow* window);
void MouseSetViewportID(ImGuiID viewport_id);
// Keyboard inputs
void KeyDown(ImGuiKeyChord key_chord);
void KeyUp(ImGuiKeyChord key_chord);
void KeyPress(ImGuiKeyChord key_chord, int count = 1);
void KeyHold(ImGuiKeyChord key_chord, float time);
void KeySetEx(ImGuiKeyChord key_chord, bool is_down, float time);
void KeyChars(const char* chars); // Input characters
void KeyCharsAppend(const char* chars); // Input characters at end of field
void KeyCharsAppendEnter(const char* chars); // Input characters at end of field, press Enter
void KeyCharsReplace(const char* chars); // Delete existing field then input characters
void KeyCharsReplaceEnter(const char* chars); // Delete existing field then input characters, press Enter
// Navigation inputs
// FIXME: Need some redesign/refactoring:
// - This was initially intended to: replace mouse action with keyboard/gamepad
// - Abstract keyboard vs gamepad actions
// However this is widely inconsistent and unfinished at this point.
void SetInputMode(ImGuiInputSource input_mode); // Mouse or Keyboard or Gamepad. In Keyboard or Gamepad mode, actions such as ItemClick or ItemInput are using nav facilities instead of Mouse.
void NavMoveTo(ImGuiTestRef ref);
void NavActivate(); // Activate current selected item: activate button, tweak sliders/drags. Equivalent of pressing Space on keyboard, ImGuiKey_GamepadFaceUp on a gamepad.
void NavInput(); // Input into select item: input sliders/drags. Equivalent of pressing Enter on keyboard, ImGuiKey_GamepadFaceDown on a gamepad.
// Scrolling
void ScrollTo(ImGuiTestRef ref, ImGuiAxis axis, float scroll_v, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None);
void ScrollToX(ImGuiTestRef ref, float scroll_x) { ScrollTo(ref, ImGuiAxis_X, scroll_x); }
void ScrollToY(ImGuiTestRef ref, float scroll_y) { ScrollTo(ref, ImGuiAxis_Y, scroll_y); }
void ScrollToTop(ImGuiTestRef ref);
void ScrollToBottom(ImGuiTestRef ref);
void ScrollToItem(ImGuiTestRef ref, ImGuiAxis axis, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None);
void ScrollToItemX(ImGuiTestRef ref);
void ScrollToItemY(ImGuiTestRef ref);
void ScrollToTabItem(ImGuiTabBar* tab_bar, ImGuiID tab_id);
bool ScrollErrorCheck(ImGuiAxis axis, float expected, float actual, int* remaining_attempts);
void ScrollVerifyScrollMax(ImGuiTestRef ref);
// Low-level queries
// - ItemInfo queries never returns a NULL pointer, instead they return an empty instance (info->IsEmpty(), info->ID == 0) and set contexted as errored.
// - You can use ImGuiTestOpFlags_NoError to do a query without marking context as errored. This is what ItemExists() does.
ImGuiTestItemInfo* ItemInfo(ImGuiTestRef ref, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None);
ImGuiTestItemInfo* ItemInfoOpenFullPath(ImGuiTestRef ref, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None);
ImGuiID ItemInfoHandleWildcardSearch(const char* wildcard_prefix_start, const char* wildcard_prefix_end, const char* wildcard_suffix_start);
ImGuiTestItemInfo* ItemInfoNull();
void GatherItems(ImGuiTestItemList* out_list, ImGuiTestRef parent, int depth = -1);
// Item/Widgets manipulation
void ItemAction(ImGuiTestAction action, ImGuiTestRef ref, ImGuiTestOpFlags flags = 0, void* action_arg = NULL);
void ItemClick(ImGuiTestRef ref, ImGuiMouseButton button = 0, ImGuiTestOpFlags flags = 0) { ItemAction(ImGuiTestAction_Click, ref, flags, (void*)(size_t)button); }
void ItemDoubleClick(ImGuiTestRef ref, ImGuiTestOpFlags flags = 0) { ItemAction(ImGuiTestAction_DoubleClick, ref, flags); }
void ItemCheck(ImGuiTestRef ref, ImGuiTestOpFlags flags = 0) { ItemAction(ImGuiTestAction_Check, ref, flags); }
void ItemUncheck(ImGuiTestRef ref, ImGuiTestOpFlags flags = 0) { ItemAction(ImGuiTestAction_Uncheck, ref, flags); }
void ItemOpen(ImGuiTestRef ref, ImGuiTestOpFlags flags = 0) { ItemAction(ImGuiTestAction_Open, ref, flags); }
void ItemClose(ImGuiTestRef ref, ImGuiTestOpFlags flags = 0) { ItemAction(ImGuiTestAction_Close, ref, flags); }
void ItemInput(ImGuiTestRef ref, ImGuiTestOpFlags flags = 0) { ItemAction(ImGuiTestAction_Input, ref, flags); }
void ItemNavActivate(ImGuiTestRef ref, ImGuiTestOpFlags flags = 0) { ItemAction(ImGuiTestAction_NavActivate, ref, flags); }
bool ItemOpenFullPath(ImGuiTestRef);
// Item/Widgets: Batch actions over an entire scope
void ItemActionAll(ImGuiTestAction action, ImGuiTestRef ref_parent, const ImGuiTestActionFilter* filter = NULL);
void ItemOpenAll(ImGuiTestRef ref_parent, int depth = -1, int passes = -1);
void ItemCloseAll(ImGuiTestRef ref_parent, int depth = -1, int passes = -1);
// Item/Widgets: Helpers to easily set a value
void ItemInputValue(ImGuiTestRef ref, int v);
void ItemInputValue(ImGuiTestRef ref, float f);
void ItemInputValue(ImGuiTestRef ref, const char* str);
// Item/Widgets: Drag and Mouse operations
void ItemHold(ImGuiTestRef ref, float time);
void ItemHoldForFrames(ImGuiTestRef ref, int frames);
void ItemDragOverAndHold(ImGuiTestRef ref_src, ImGuiTestRef ref_dst);
void ItemDragAndDrop(ImGuiTestRef ref_src, ImGuiTestRef ref_dst, ImGuiMouseButton button = 0);
void ItemDragWithDelta(ImGuiTestRef ref_src, ImVec2 pos_delta);
// Item/Widgets: Status query
bool ItemExists(ImGuiTestRef ref);
bool ItemIsChecked(ImGuiTestRef ref);
bool ItemIsOpened(ImGuiTestRef ref);
void ItemVerifyCheckedIfAlive(ImGuiTestRef ref, bool checked);
// Helpers for Tab Bars widgets
void TabClose(ImGuiTestRef ref);
bool TabBarCompareOrder(ImGuiTabBar* tab_bar, const char** tab_order);
// Helpers for MenuBar and Menus widgets
// - e.g. MenuCheck("File/Options/Enable grid");
void MenuAction(ImGuiTestAction action, ImGuiTestRef ref);
void MenuActionAll(ImGuiTestAction action, ImGuiTestRef ref_parent);
void MenuClick(ImGuiTestRef ref) { MenuAction(ImGuiTestAction_Click, ref); }
void MenuCheck(ImGuiTestRef ref) { MenuAction(ImGuiTestAction_Check, ref); }
void MenuUncheck(ImGuiTestRef ref) { MenuAction(ImGuiTestAction_Uncheck, ref); }
void MenuCheckAll(ImGuiTestRef ref_parent) { MenuActionAll(ImGuiTestAction_Check, ref_parent); }
void MenuUncheckAll(ImGuiTestRef ref_parent) { MenuActionAll(ImGuiTestAction_Uncheck, ref_parent); }
// Helpers for Combo Boxes
void ComboClick(ImGuiTestRef ref);
void ComboClickAll(ImGuiTestRef ref);
// Helpers for Tables
void TableOpenContextMenu(ImGuiTestRef ref, int column_n = -1);
ImGuiSortDirection TableClickHeader(ImGuiTestRef ref, const char* label, ImGuiKeyChord key_mods = 0);
void TableSetColumnEnabled(ImGuiTestRef ref, const char* label, bool enabled);
void TableResizeColumn(ImGuiTestRef ref, int column_n, float width);
const ImGuiTableSortSpecs* TableGetSortSpecs(ImGuiTestRef ref);
// Viewports
// IMPORTANT: Those function may alter Platform state (unless using the "Mock Viewport" backend). Use carefully.
// Those are mostly useful to simulate OS actions and testing of viewport-specific features, may not be useful to most users.
#ifdef IMGUI_HAS_VIEWPORT
//void ViewportPlatform_SetWindowPos(ImGuiViewport* viewport, const ImVec2& pos);
//void ViewportPlatform_SetWindowSize(ImGuiViewport* viewport, const ImVec2& size);
void ViewportPlatform_SetWindowFocus(ImGuiViewport* viewport);
void ViewportPlatform_CloseWindow(ImGuiViewport* viewport);
#endif
// Docking
#ifdef IMGUI_HAS_DOCK
void DockClear(const char* window_name, ...);
void DockInto(ImGuiTestRef src_id, ImGuiTestRef dst_id, ImGuiDir split_dir = ImGuiDir_None, bool is_outer_docking = false, ImGuiTestOpFlags flags = 0);
void UndockNode(ImGuiID dock_id);
void UndockWindow(const char* window_name);
bool WindowIsUndockedOrStandalone(ImGuiWindow* window);
bool DockIdIsUndockedOrStandalone(ImGuiID dock_id);
void DockNodeHideTabBar(ImGuiDockNode* node, bool hidden);
#endif
// Performances Measurement (use along with Dear ImGui Perf Tool)
void PerfCalcRef();
void PerfCapture(const char* category = NULL, const char* test_name = NULL, const char* csv_file = NULL);
// Obsolete functions
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
// Obsoleted 2022/10/11
ImGuiID GetIDByInt(int n); // Prefer using "$$123"
ImGuiID GetIDByInt(int n, ImGuiTestRef seed_ref);
ImGuiID GetIDByPtr(void* p); // Prefer using "$$(ptr)0xFFFFFFFF"
ImGuiID GetIDByPtr(void* p, ImGuiTestRef seed_ref);
// Obsoleted 2022/09/26
void KeyModDown(ImGuiModFlags mods) { KeyDown(mods); }
void KeyModUp(ImGuiModFlags mods) { KeyUp(mods); }
void KeyModPress(ImGuiModFlags mods) { KeyPress(mods); }
#endif
// [Internal]
// FIXME: Aim to remove this system...
void ForeignWindowsHideOverPos(ImVec2 pos, ImGuiWindow** ignore_list);
void ForeignWindowsUnhideAll();
};
//-------------------------------------------------------------------------
// [SECTION] Debugging macros (IM_SUSPEND_TESTFUNC)
//-------------------------------------------------------------------------
// Debug: Temporarily suspend TestFunc to let user interactively inspect the GUI state (user will need to press the "Continue" button to resume TestFunc execution)
#define IM_SUSPEND_TESTFUNC() do { if (ctx->SuspendTestFunc(__FILE__, __LINE__)) return; } while (0)
//-------------------------------------------------------------------------
// [SECTION] Testing/Checking macros: IM_CHECK(), IM_ERRORF() etc.
//-------------------------------------------------------------------------
// Helpers used by IM_CHECK_OP() macros.
// ImGuiTestEngine_GetTempStringBuilder() returns a shared instance of ImGuiTextBuffer to recycle memory allocations
template<typename T> void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, T v) { buf->append("???"); IM_UNUSED(v); } // FIXME-TESTS: Could improve with some template magic
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, const char* v) { buf->appendf("\"%s\"", v); }
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, bool v) { buf->append(v ? "true" : "false"); }
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImS8 v) { buf->appendf("%d", v); }
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImU8 v) { buf->appendf("%u", v); }
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImS16 v) { buf->appendf("%hd", v); }
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImU16 v) { buf->appendf("%hu", v); }
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImS32 v) { buf->appendf("%d", v); }
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImU32 v) { buf->appendf("0x%08X", v); } // Assuming ImGuiID
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImS64 v) { buf->appendf("%lld", v); }
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImU64 v) { buf->appendf("%llu", v); }
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, float v) { buf->appendf("%.3f", v); }
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, double v) { buf->appendf("%f", v); }
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImVec2 v) { buf->appendf("(%.3f, %.3f)", v.x, v.y); }
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, const void* v) { buf->appendf("%p", v); }
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImGuiWindow* v){ if (v) buf->appendf("\"%s\"", v->Name); else buf->append("NULL"); }
// We embed every macro in a do {} while(0) statement as a trick to allow using them as regular single statement, e.g. if (XXX) IM_CHECK(A); else IM_CHECK(B)
// We leave the IM_DEBUG_BREAK() outside of the check function to step out faster when using a debugger. It also has the benefit of being lighter than an IM_ASSERT().
#define IM_CHECK(_EXPR) do { bool res = (bool)(_EXPR); if (ImGuiTestEngine_Check(__FILE__, __func__, __LINE__, ImGuiTestCheckFlags_None, res, #_EXPR)) { IM_DEBUG_BREAK(); } if (!res) return; } while (0)
#define IM_CHECK_NO_RET(_EXPR) do { bool res = (bool)(_EXPR); if (ImGuiTestEngine_Check(__FILE__, __func__, __LINE__, ImGuiTestCheckFlags_None, res, #_EXPR)) { IM_DEBUG_BREAK(); } } while (0)
#define IM_CHECK_SILENT(_EXPR) do { bool res = (bool)(_EXPR); if (ImGuiTestEngine_Check(__FILE__, __func__, __LINE__, ImGuiTestCheckFlags_SilentSuccess, res, #_EXPR)) { IM_DEBUG_BREAK(); } if (!res) return; } while (0)
#define IM_CHECK_RETV(_EXPR,_RETV) do { bool res = (bool)(_EXPR); if (ImGuiTestEngine_Check(__FILE__, __func__, __LINE__, ImGuiTestCheckFlags_None, res, #_EXPR)) { IM_DEBUG_BREAK(); } if (!res) return _RETV; } while (0)
#define IM_CHECK_SILENT_RETV(_EXPR,_RETV) do { bool res = (bool)(_EXPR); if (ImGuiTestEngine_Check(__FILE__, __func__, __LINE__, ImGuiTestCheckFlags_SilentSuccess, res, #_EXPR)) { IM_DEBUG_BREAK(); } if (!res) return _RETV; } while (0)
#define IM_ERRORF(_FMT,...) do { if (ImGuiTestEngine_Error(__FILE__, __func__, __LINE__, ImGuiTestCheckFlags_None, _FMT, __VA_ARGS__)) { IM_DEBUG_BREAK(); } } while (0)
#define IM_ERRORF_NOHDR(_FMT,...) do { if (ImGuiTestEngine_Error(NULL, NULL, 0, ImGuiTestCheckFlags_None, _FMT, __VA_ARGS__)) { IM_DEBUG_BREAK(); } } while (0)
// Those macros allow us to print out the values of both LHS and RHS expressions involved in a check.
#define IM_CHECK_OP(_LHS, _RHS, _OP, _RETURN) \
do \
{ \
auto __lhs = _LHS; /* Cache to avoid side effects */ \
auto __rhs = _RHS; \
bool __res = __lhs _OP __rhs; \
ImGuiTextBuffer* expr_buf = ImGuiTestEngine_GetTempStringBuilder(); \
expr_buf->append(#_LHS " ["); \
ImGuiTestEngineUtil_appendf_auto(expr_buf, __lhs); \
expr_buf->append("] " #_OP " " #_RHS " ["); \
ImGuiTestEngineUtil_appendf_auto(expr_buf, __rhs); \
expr_buf->append("]"); \
if (ImGuiTestEngine_Check(__FILE__, __func__, __LINE__, ImGuiTestCheckFlags_None, __res, expr_buf->c_str())) \
IM_ASSERT(__res); \
if (_RETURN && !__res) \
return; \
} while (0)
#define IM_CHECK_STR_OP(_LHS, _RHS, _OP, _RETURN, _FLAGS) \
do \
{ \
bool __res; \
if (ImGuiTestEngine_CheckStrOp(__FILE__, __func__, __LINE__, _FLAGS, #_OP, #_LHS, _LHS, #_RHS, _RHS, &__res)) \
IM_ASSERT(__res); \
if (_RETURN && !__res) \
return; \
} while (0)
// Scalar compares
#define IM_CHECK_EQ(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, ==, true) // Equal
#define IM_CHECK_NE(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, !=, true) // Not Equal
#define IM_CHECK_LT(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, < , true) // Less Than
#define IM_CHECK_LE(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, <=, true) // Less or Equal
#define IM_CHECK_GT(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, > , true) // Greater Than
#define IM_CHECK_GE(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, >=, true) // Greater or Equal
// Scalar compares, without return on failure
#define IM_CHECK_EQ_NO_RET(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, ==, false) // Equal
#define IM_CHECK_NE_NO_RET(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, !=, false) // Not Equal
#define IM_CHECK_LT_NO_RET(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, < , false) // Less Than
#define IM_CHECK_LE_NO_RET(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, <=, false) // Less or Equal
#define IM_CHECK_GT_NO_RET(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, > , false) // Greater Than
#define IM_CHECK_GE_NO_RET(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, >=, false) // Greater or Equal
// String compares
#define IM_CHECK_STR_EQ(_LHS, _RHS) IM_CHECK_STR_OP(_LHS, _RHS, ==, true, ImGuiTestCheckFlags_None)
#define IM_CHECK_STR_NE(_LHS, _RHS) IM_CHECK_STR_OP(_LHS, _RHS, !=, true, ImGuiTestCheckFlags_None)
#define IM_CHECK_STR_EQ_NO_RET(_LHS, _RHS) IM_CHECK_STR_OP(_LHS, _RHS, ==, false, ImGuiTestCheckFlags_None)
#define IM_CHECK_STR_NE_NO_RET(_LHS, _RHS) IM_CHECK_STR_OP(_LHS, _RHS, !=, false, ImGuiTestCheckFlags_None)
#define IM_CHECK_STR_EQ_SILENT(_LHS, _RHS) IM_CHECK_STR_OP(_LHS, _RHS, ==, true, ImGuiTestCheckFlags_SilentSuccess)
// Floating point compares
#define IM_CHECK_FLOAT_EQ_EPS(_LHS, _RHS) IM_CHECK_LE(ImFabs(_LHS - (_RHS)), FLT_EPSILON) // Float Equal
#define IM_CHECK_FLOAT_NEAR(_LHS, _RHS, _EPS) IM_CHECK_LE(ImFabs(_LHS - (_RHS)), _EPS)
#define IM_CHECK_FLOAT_NEAR_NO_RET(_LHS, _RHS, _E) IM_CHECK_LE_NO_RET(ImFabs(_LHS - (_RHS)), _E)
//-------------------------------------------------------------------------
#if defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GNUC__)
#pragma GCC diagnostic pop
#endif

View File

@@ -0,0 +1,167 @@
// dear imgui test engine
// (coroutine interface + optional implementation)
// Read https://github.com/ocornut/imgui_test_engine/wiki/Setting-Up
#include "imgui_te_coroutine.h"
#include "imgui.h"
#ifdef _MSC_VER
#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
#endif
//------------------------------------------------------------------------
// Coroutine implementation using std::thread
// This implements a coroutine using std::thread, with a helper thread for each coroutine (with serialised execution, so threads never actually run concurrently)
//------------------------------------------------------------------------
#if IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL
#include "imgui_te_utils.h"
#include "thirdparty/Str/Str.h"
#include <thread>
#include <mutex>
#include <condition_variable>
struct Coroutine_ImplStdThreadData
{
std::thread* Thread; // The thread this coroutine is using
std::condition_variable StateChange; // Condition variable notified when the coroutine state changes
std::mutex StateMutex; // Mutex to protect coroutine state
bool CoroutineRunning; // Is the coroutine currently running? Lock StateMutex before access and notify StateChange on change
bool CoroutineTerminated; // Has the coroutine terminated? Lock StateMutex before access and notify StateChange on change
Str64 Name; // The name of this coroutine
};
// The coroutine executing on the current thread (if it is a coroutine thread)
static thread_local Coroutine_ImplStdThreadData* GThreadCoroutine = NULL;
// The main function for a coroutine thread
static void CoroutineThreadMain(Coroutine_ImplStdThreadData* data, ImGuiTestCoroutineMainFunc func, void* ctx)
{
// Set our thread name
ImThreadSetCurrentThreadDescription(data->Name.c_str());
// Set the thread coroutine
GThreadCoroutine = data;
// Wait for initial Run()
while (1)
{
std::unique_lock<std::mutex> lock(data->StateMutex);
if (data->CoroutineRunning)
break;
data->StateChange.wait(lock);
}
// Run user code, which will then call Yield() when it wants to yield control
func(ctx);
// Mark as terminated
{
std::lock_guard<std::mutex> lock(data->StateMutex);
data->CoroutineTerminated = true;
data->CoroutineRunning = false;
data->StateChange.notify_all();
}
}
static ImGuiTestCoroutineHandle Coroutine_ImplStdThread_Create(ImGuiTestCoroutineMainFunc* func, const char* name, void* ctx)
{
Coroutine_ImplStdThreadData* data = new Coroutine_ImplStdThreadData();
data->Name = name;
data->CoroutineRunning = false;
data->CoroutineTerminated = false;
data->Thread = new std::thread(CoroutineThreadMain, data, func, ctx);
return (ImGuiTestCoroutineHandle)data;
}
static void Coroutine_ImplStdThread_Destroy(ImGuiTestCoroutineHandle handle)
{
Coroutine_ImplStdThreadData* data = (Coroutine_ImplStdThreadData*)handle;
IM_ASSERT(data->CoroutineTerminated); // The coroutine needs to run to termination otherwise it may leak all sorts of things and this will deadlock
if (data->Thread)
{
data->Thread->join();
delete data->Thread;
data->Thread = NULL;
}
delete data;
data = NULL;
}
// Run the coroutine until the next call to Yield(). Returns TRUE if the coroutine yielded, FALSE if it terminated (or had previously terminated)
static bool Coroutine_ImplStdThread_Run(ImGuiTestCoroutineHandle handle)
{
Coroutine_ImplStdThreadData* data = (Coroutine_ImplStdThreadData*)handle;
// Wake up coroutine thread
{
std::lock_guard<std::mutex> lock(data->StateMutex);
if (data->CoroutineTerminated)
return false; // Coroutine has already finished
data->CoroutineRunning = true;
data->StateChange.notify_all();
}
// Wait for coroutine to stop
while (1)
{
std::unique_lock<std::mutex> lock(data->StateMutex);
if (!data->CoroutineRunning)
{
// Breakpoint here to catch the point where we return from the coroutine
if (data->CoroutineTerminated)
return false; // Coroutine finished
break;
}
data->StateChange.wait(lock);
}
return true;
}
// Yield the current coroutine (can only be called from a coroutine)
static void Coroutine_ImplStdThread_Yield()
{
IM_ASSERT(GThreadCoroutine); // This can only be called from a coroutine thread
Coroutine_ImplStdThreadData* data = GThreadCoroutine;
// Flag that we are not running any more
{
std::lock_guard<std::mutex> lock(data->StateMutex);
data->CoroutineRunning = false;
data->StateChange.notify_all();
}
// At this point the thread that called RunCoroutine() will leave the "Wait for coroutine to stop" loop
// Wait until we get started up again
while (1)
{
std::unique_lock<std::mutex> lock(data->StateMutex);
if (data->CoroutineRunning)
break; // Breakpoint here if you want to catch the point where execution of this coroutine resumes
data->StateChange.wait(lock);
}
}
ImGuiTestCoroutineInterface* Coroutine_ImplStdThread_GetInterface()
{
static ImGuiTestCoroutineInterface intf;
intf.CreateFunc = Coroutine_ImplStdThread_Create;
intf.DestroyFunc = Coroutine_ImplStdThread_Destroy;
intf.RunFunc = Coroutine_ImplStdThread_Run;
intf.YieldFunc = Coroutine_ImplStdThread_Yield;
return &intf;
}
#endif // #if IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL

View File

@@ -0,0 +1,56 @@
// dear imgui test engine
// (coroutine interface + optional implementation)
// Read https://github.com/ocornut/imgui_test_engine/wiki/Setting-Up
#pragma once
#ifndef IMGUI_VERSION
#include "imgui.h"
#endif
//------------------------------------------------------------------------
// Coroutine abstraction
//------------------------------------------------------------------------
// Coroutines should be used like this:
// ImGuiTestCoroutineHandle handle = CoroutineCreate(<func>, <name>, <ctx>); // name being for debugging, and ctx being an arbitrary user context pointer
// while (CoroutineRun(handle)) { <do other stuff };
// CoroutineDestroy(handle);
// The coroutine code itself should call CoroutineYieldFunc() whenever it wants to yield control back to the main thread.
//------------------------------------------------------------------------
// An arbitrary handle used internally to represent coroutines (NULL indicates no handle)
typedef void* ImGuiTestCoroutineHandle;
// A coroutine main function
typedef void (ImGuiTestCoroutineMainFunc)(void* data);
// Coroutine support interface
// Your app needs to return and implement this.
// You can '#define IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL 1' in your imconfig file to use a default implementation using std::thread
// Documentation: https://github.com/ocornut/imgui_test_engine/wiki/Setting-Up
struct IMGUI_API ImGuiTestCoroutineInterface
{
// Create a new coroutine
ImGuiTestCoroutineHandle (*CreateFunc)(ImGuiTestCoroutineMainFunc* func, const char* name, void* data);
// Destroy a coroutine (which must have completed first)
void (*DestroyFunc)(ImGuiTestCoroutineHandle handle);
// Run a coroutine until it yields or finishes, returning false if finished
bool (*RunFunc)(ImGuiTestCoroutineHandle handle);
// Yield from a coroutine back to the caller, preserving coroutine state
void (*YieldFunc)();
};
//------------------------------------------------------------------------
// Coroutine implementation using std::thread
// The "coroutine" thread and user's main thread will always block on each other (both threads will NEVER run in parallel)
// It is just an implementation convenience that we provide an implementation using std::thread as it is widely available/standard.
//------------------------------------------------------------------------
#if IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL
IMGUI_API ImGuiTestCoroutineInterface* Coroutine_ImplStdThread_GetInterface();
#endif // #if IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,429 @@
// dear imgui test engine
// (core)
// This is the interface that your initial setup (app init, main loop) will mostly be using.
// Actual tests will mostly use the interface of imgui_te_context.h
#pragma once
#include "imgui.h"
#include "imgui_internal.h" // ImPool<>, ImRect, ImGuiItemStatusFlags, ImFormatString
#include "imgui_te_utils.h" // ImFuncPtr
#include "imgui_capture_tool.h" // ImGuiScreenCaptureFunc
//-------------------------------------------------------------------------
// Forward Declarations
//-------------------------------------------------------------------------
struct ImGuiTest; // Data for a test registered with IM_REGISTER_TEST()
struct ImGuiTestContext; // Context while a test is running
struct ImGuiTestCoroutineInterface; // Interface to expose coroutine functions (imgui_te_coroutine provides a default implementation for C++11 using std::thread, but you may use your own)
struct ImGuiTestEngine; // Test engine instance
struct ImGuiTestEngineIO; // Test engine public I/O
struct ImGuiTestItemInfo; // Info queried from item (id, geometry, status flags, debug label)
struct ImGuiTestItemList; // A list of items
struct ImGuiTestInputs; // Simulated user inputs (will be fed into ImGuiIO by the test engine)
struct ImGuiTestRunTask; // A queued test (test + runflags)
typedef int ImGuiTestFlags; // Flags: See ImGuiTestFlags_
typedef int ImGuiTestCheckFlags; // Flags: See ImGuiTestCheckFlags_
typedef int ImGuiTestLogFlags; // Flags: See ImGuiTestLogFlags_
typedef int ImGuiTestRunFlags; // Flags: See ImGuiTestRunFlags_
enum ImGuiTestActiveFunc : int;
enum ImGuiTestGroup : int;
enum ImGuiTestRunSpeed : int;
enum ImGuiTestStatus : int;
enum ImGuiTestVerboseLevel : int;
enum ImGuiTestEngineExportFormat : int;
//-------------------------------------------------------------------------
// Types
//-------------------------------------------------------------------------
// Stored in ImGuiTestContext: where we are currently running GuiFunc or TestFunc
enum ImGuiTestActiveFunc : int
{
ImGuiTestActiveFunc_None,
ImGuiTestActiveFunc_GuiFunc,
ImGuiTestActiveFunc_TestFunc
};
enum ImGuiTestRunSpeed : int
{
ImGuiTestRunSpeed_Fast = 0, // Run tests as fast as possible (teleport mouse, skip delays, etc.)
ImGuiTestRunSpeed_Normal = 1, // Run tests at human watchable speed (for debugging)
ImGuiTestRunSpeed_Cinematic = 2, // Run tests with pauses between actions (for e.g. tutorials)
ImGuiTestRunSpeed_COUNT
};
enum ImGuiTestVerboseLevel : int
{
ImGuiTestVerboseLevel_Silent = 0, // -v0
ImGuiTestVerboseLevel_Error = 1, // -v1
ImGuiTestVerboseLevel_Warning = 2, // -v2
ImGuiTestVerboseLevel_Info = 3, // -v3
ImGuiTestVerboseLevel_Debug = 4, // -v4
ImGuiTestVerboseLevel_Trace = 5,
ImGuiTestVerboseLevel_COUNT
};
// Test status (stored in ImGuiTest)
enum ImGuiTestStatus : int
{
ImGuiTestStatus_Unknown = -1,
ImGuiTestStatus_Success = 0,
ImGuiTestStatus_Queued = 1,
ImGuiTestStatus_Running = 2,
ImGuiTestStatus_Error = 3,
ImGuiTestStatus_Suspended = 4,
ImGuiTestStatus_COUNT
};
// Test group: this is mostly used to categorize tests in our testing UI. (Stored in ImGuiTest)
enum ImGuiTestGroup : int
{
ImGuiTestGroup_Unknown = -1,
ImGuiTestGroup_Tests = 0,
ImGuiTestGroup_Perfs = 1,
ImGuiTestGroup_COUNT
};
// Flags (stored in ImGuiTest)
enum ImGuiTestFlags_
{
ImGuiTestFlags_None = 0,
ImGuiTestFlags_NoGuiWarmUp = 1 << 0, // Disable running the GUI func for 2 frames before starting test code. For tests which absolutely need to start before GuiFunc.
ImGuiTestFlags_NoAutoFinish = 1 << 1, // By default, tests with no TestFunc (only a GuiFunc) will end after warmup. Setting this require test to call ctx->Finish().
ImGuiTestFlags_NoRecoveryWarnings = 1 << 2 // Disable state recovery warnings (missing End/Pop calls etc.) for tests which may rely on those.
//ImGuiTestFlags_RequireViewports = 1 << 10
};
// Flags for IM_CHECK* macros.
enum ImGuiTestCheckFlags_
{
ImGuiTestCheckFlags_None = 0,
ImGuiTestCheckFlags_SilentSuccess = 1 << 0
};
// Flags for ImGuiTestContext::Log* functions.
enum ImGuiTestLogFlags_
{
ImGuiTestLogFlags_None = 0,
ImGuiTestLogFlags_NoHeader = 1 << 0 // Do not display frame count and depth padding
};
enum ImGuiTestRunFlags_
{
ImGuiTestRunFlags_None = 0,
ImGuiTestRunFlags_GuiFuncDisable = 1 << 0, // Used internally to temporarily disable the GUI func (at the end of a test, etc)
ImGuiTestRunFlags_GuiFuncOnly = 1 << 1, // Set when user selects "Run GUI func"
ImGuiTestRunFlags_NoSuccessMsg = 1 << 2,
ImGuiTestRunFlags_EnableRawInputs = 1 << 3, // Disable input submission to let test submission raw input event (in order to test e.g. IO queue)
ImGuiTestRunFlags_RunFromGui = 1 << 4, // Test ran manually from GUI, will disable watchdog.
ImGuiTestRunFlags_RunFromCommandLine= 1 << 5, // Test queued from command-line.
// Flags for ImGuiTestContext::RunChildTest()
ImGuiTestRunFlags_NoError = 1 << 10,
ImGuiTestRunFlags_ShareVars = 1 << 11, // Share generic vars and custom vars between child and parent tests (custom vars need to be same type)
ImGuiTestRunFlags_ShareTestContext = 1 << 12, // Share ImGuiTestContext instead of creating a new one (unsure what purpose this may be useful for yet)
// TODO: Add GuiFunc options
};
//-------------------------------------------------------------------------
// Functions
//-------------------------------------------------------------------------
// Hooks for core imgui/ library (generally called via macros)
extern void ImGuiTestEngineHook_ItemAdd(ImGuiContext* ui_ctx, ImGuiID id, const ImRect& bb, const ImGuiLastItemData* item_data);
#if IMGUI_VERSION_NUM < 18934
extern void ImGuiTestEngineHook_ItemAdd(ImGuiContext* ui_ctx, const ImRect& bb, ImGuiID id);
#endif
#ifdef IMGUI_HAS_IMSTR
extern void ImGuiTestEngineHook_ItemInfo(ImGuiContext* ui_ctx, ImGuiID id, ImStrv label, ImGuiItemStatusFlags flags);
#else
extern void ImGuiTestEngineHook_ItemInfo(ImGuiContext* ui_ctx, ImGuiID id, const char* label, ImGuiItemStatusFlags flags);
#endif
extern void ImGuiTestEngineHook_Log(ImGuiContext* ui_ctx, const char* fmt, ...);
extern const char* ImGuiTestEngine_FindItemDebugLabel(ImGuiContext* ui_ctx, ImGuiID id);
// Functions (generally called via IM_CHECK() macros)
IMGUI_API bool ImGuiTestEngine_Check(const char* file, const char* func, int line, ImGuiTestCheckFlags flags, bool result, const char* expr);
IMGUI_API bool ImGuiTestEngine_CheckStrOp(const char* file, const char* func, int line, ImGuiTestCheckFlags flags, const char* op, const char* lhs_var, const char* lhs_value, const char* rhs_var, const char* rhs_value, bool* out_result);
IMGUI_API bool ImGuiTestEngine_Error(const char* file, const char* func, int line, ImGuiTestCheckFlags flags, const char* fmt, ...);
IMGUI_API void ImGuiTestEngine_AssertLog(const char* expr, const char* file, const char* function, int line);
IMGUI_API ImGuiTextBuffer* ImGuiTestEngine_GetTempStringBuilder();
//-------------------------------------------------------------------------
// ImGuiTestEngine API
//-------------------------------------------------------------------------
// Functions: Initialization
IMGUI_API ImGuiTestEngine* ImGuiTestEngine_CreateContext(); // Create test engine
IMGUI_API void ImGuiTestEngine_DestroyContext(ImGuiTestEngine* engine); // Destroy test engine. Call after ImGui::DestroyContext() so test engine specific ini data gets saved.
IMGUI_API void ImGuiTestEngine_Start(ImGuiTestEngine* engine, ImGuiContext* ui_ctx); // Bind to a dear imgui context. Start coroutine.
IMGUI_API void ImGuiTestEngine_Stop(ImGuiTestEngine* engine); // Stop coroutine and export if any. (Unbind will lazily happen on context shutdown)
IMGUI_API void ImGuiTestEngine_PostSwap(ImGuiTestEngine* engine); // Call every frame after framebuffer swap, will process screen capture and call test_io.ScreenCaptureFunc()
IMGUI_API ImGuiTestEngineIO& ImGuiTestEngine_GetIO(ImGuiTestEngine* engine);
// Macros: Register Test
#define IM_REGISTER_TEST(_ENGINE, _CATEGORY, _NAME) ImGuiTestEngine_RegisterTest(_ENGINE, _CATEGORY, _NAME, __FILE__, __LINE__)
IMGUI_API ImGuiTest* ImGuiTestEngine_RegisterTest(ImGuiTestEngine* engine, const char* category, const char* name, const char* src_file = NULL, int src_line = 0); // Prefer calling IM_REGISTER_TEST()
// Functions: Main
IMGUI_API void ImGuiTestEngine_QueueTest(ImGuiTestEngine* engine, ImGuiTest* test, ImGuiTestRunFlags run_flags = 0);
IMGUI_API void ImGuiTestEngine_QueueTests(ImGuiTestEngine* engine, ImGuiTestGroup group, const char* filter = NULL, ImGuiTestRunFlags run_flags = 0);
IMGUI_API bool ImGuiTestEngine_TryAbortEngine(ImGuiTestEngine* engine);
IMGUI_API void ImGuiTestEngine_AbortCurrentTest(ImGuiTestEngine* engine);
IMGUI_API ImGuiTest* ImGuiTestEngine_FindTestByName(ImGuiTestEngine* engine, const char* category, const char* name);
// Functions: Status Queries
// FIXME: Clarify API to avoid function calls vs raw bools in ImGuiTestEngineIO
IMGUI_API bool ImGuiTestEngine_IsTestQueueEmpty(ImGuiTestEngine* engine);
IMGUI_API bool ImGuiTestEngine_IsUsingSimulatedInputs(ImGuiTestEngine* engine);
IMGUI_API void ImGuiTestEngine_GetResult(ImGuiTestEngine* engine, int& count_tested, int& success_count);
IMGUI_API void ImGuiTestEngine_GetTestList(ImGuiTestEngine* engine, ImVector<ImGuiTest*>* out_tests);
IMGUI_API void ImGuiTestEngine_GetTestQueue(ImGuiTestEngine* engine, ImVector<ImGuiTestRunTask>* out_tests);
// Functions: Crash Handling
// Ensure past test results are properly exported even if application crash during a test.
IMGUI_API void ImGuiTestEngine_InstallDefaultCrashHandler(); // Install default crash handler (if you don't have one)
IMGUI_API void ImGuiTestEngine_CrashHandler(); // Default crash handler, should be called from a custom crash handler if such exists
//-----------------------------------------------------------------------------
// IO structure to configure the test engine
//-----------------------------------------------------------------------------
// Function bound to right-clicking on a test and selecting "Open source" in the UI
// - Easy: you can make this function call OS shell to "open" the file (e.g. ImOsOpenInShell() helper).
// - Better: bind this function to a custom setup which can pass line number to a text editor (e.g. see 'imgui_test_suite/tools/win32_open_with_sublime.cmd' example)
typedef void (ImGuiTestEngineSrcFileOpenFunc)(const char* filename, int line_no, void* user_data);
struct IMGUI_API ImGuiTestEngineIO
{
//-------------------------------------------------------------------------
// Functions
//-------------------------------------------------------------------------
// Options: Functions
ImGuiTestCoroutineInterface* CoroutineFuncs = NULL; // (Required) Coroutine functions (see imgui_te_coroutines.h)
ImFuncPtr(ImGuiTestEngineSrcFileOpenFunc) SrcFileOpenFunc = NULL; // (Optional) To open source files from test engine UI
ImFuncPtr(ImGuiScreenCaptureFunc) ScreenCaptureFunc = NULL; // (Optional) To capture graphics output (application _MUST_ call ImGuiTestEngine_PostSwap() function after swapping is framebuffer)
void* SrcFileOpenUserData = NULL; // (Optional) User data for SrcFileOpenFunc
void* ScreenCaptureUserData = NULL; // (Optional) User data for ScreenCaptureFunc
// Options: Main
bool ConfigSavedSettings = true; // Load/Save settings in main context .ini file.
ImGuiTestRunSpeed ConfigRunSpeed = ImGuiTestRunSpeed_Fast; // Run tests in fast/normal/cinematic mode
bool ConfigStopOnError = false; // Stop queued tests on test error
bool ConfigBreakOnError = false; // Break debugger on test error by calling IM_DEBUG_BREAK()
bool ConfigKeepGuiFunc = false; // Keep test GUI running at the end of the test
ImGuiTestVerboseLevel ConfigVerboseLevel = ImGuiTestVerboseLevel_Warning;
ImGuiTestVerboseLevel ConfigVerboseLevelOnError = ImGuiTestVerboseLevel_Info;
bool ConfigLogToTTY = false;
bool ConfigLogToDebugger = false;
bool ConfigRestoreFocusAfterTests = true;// Restore focus back after running tests
bool ConfigCaptureEnabled = true; // Master enable flags for capturing and saving captures. Disable to avoid e.g. lengthy saving of large PNG files.
bool ConfigCaptureOnError = false;
bool ConfigNoThrottle = false; // Disable vsync for performance measurement or fast test running
bool ConfigMouseDrawCursor = true; // Enable drawing of Dear ImGui software mouse cursor when running tests
float ConfigFixedDeltaTime = 0.0f; // Use fixed delta time instead of calculating it from wall clock
int PerfStressAmount = 1; // Integer to scale the amount of items submitted in test
char GitBranchName[64] = ""; // e.g. fill in branch name (e.g. recorded in perf samples .csv)
// Options: Speed of user simulation
float MouseSpeed = 600.0f; // Mouse speed (pixel/second) when not running in fast mode
float MouseWobble = 0.25f; // (0.0f..1.0f) How much wobble to apply to the mouse (pixels per pixel of move distance) when not running in fast mode
float ScrollSpeed = 1400.0f; // Scroll speed (pixel/second) when not running in fast mode
float TypingSpeed = 20.0f; // Char input speed (characters/second) when not running in fast mode
float ActionDelayShort = 0.15f; // Time between short actions
float ActionDelayStandard = 0.40f; // Time between most actions
// Options: Screen/video capture
char VideoCaptureEncoderPath[256] = ""; // Video encoder executable path, e.g. "path/to/ffmpeg.exe".
char VideoCaptureEncoderParams[256] = "";// Video encoder parameters for .MP4 captures, e.g. see IMGUI_CAPTURE_DEFAULT_VIDEO_PARAMS_FOR_FFMPEG
char GifCaptureEncoderParams[512] = ""; // Video encoder parameters for .GIF captures, e.g. see IMGUI_CAPTURE_DEFAULT_GIF_PARAMS_FOR_FFMPEG
char VideoCaptureExtension[8] = ".mp4"; // Video file extension (default, may be overridden by test).
// Options: Watchdog. Set values to FLT_MAX to disable.
// Interactive GUI applications that may be slower tend to use higher values.
float ConfigWatchdogWarning = 30.0f; // Warn when a test exceed this time (in second)
float ConfigWatchdogKillTest = 60.0f; // Attempt to stop running a test when exceeding this time (in second)
float ConfigWatchdogKillApp = FLT_MAX; // Stop application when exceeding this time (in second)
// Options: Export
// While you can manually call ImGuiTestEngine_Export(), registering filename/format here ensure the crash handler will always export if application crash.
const char* ExportResultsFilename = NULL;
ImGuiTestEngineExportFormat ExportResultsFormat = (ImGuiTestEngineExportFormat)0;
// Options: Sanity Checks
bool CheckDrawDataIntegrity = false; // Check ImDrawData integrity (buffer count, etc.). Currently cheap but may become a slow operation.
//-------------------------------------------------------------------------
// Output
//-------------------------------------------------------------------------
// Output: State of test engine
bool IsRunningTests = false;
bool IsRequestingMaxAppSpeed = false; // When running in fast mode: request app to skip vsync or even skip rendering if it wants
bool IsCapturing = false; // Capture is in progress
};
//-------------------------------------------------------------------------
// ImGuiTestItemInfo
//-------------------------------------------------------------------------
// Information about a given item or window, result of an ItemInfo() or WindowInfo() query
struct ImGuiTestItemInfo
{
int RefCount : 8; // User can increment this if they want to hold on the result pointer across frames, otherwise the task will be GC-ed.
unsigned int NavLayer : 1; // Nav layer of the item (ImGuiNavLayer)
int Depth : 16; // Depth from requested parent id. 0 == ID is immediate child of requested parent id.
int TimestampMain = -1; // Timestamp of main result (all fields)
int TimestampStatus = -1; // Timestamp of StatusFlags
ImGuiID ID = 0; // Item ID
ImGuiID ParentID = 0; // Item Parent ID (value at top of the ID stack)
ImGuiWindow* Window = NULL; // Item Window
ImRect RectFull = ImRect(); // Item Rectangle
ImRect RectClipped = ImRect(); // Item Rectangle (clipped with window->ClipRect at time of item submission)
ImGuiItemFlags InFlags = 0; // Item flags
ImGuiItemStatusFlags StatusFlags = 0; // Item Status flags (fully updated for some items only, compare TimestampStatus to FrameCount)
char DebugLabel[32] = {}; // Shortened label for debugging purpose
ImGuiTestItemInfo() { RefCount = 0; NavLayer = 0; Depth = 0; }
bool IsEmpty() const { return ID == 0; }
};
// Result of an GatherItems() query
struct IMGUI_API ImGuiTestItemList
{
ImPool<ImGuiTestItemInfo> Pool;
void Clear() { Pool.Clear(); }
void Reserve(int capacity) { Pool.Reserve(capacity); }
int GetSize() const { return Pool.GetMapSize(); }
const ImGuiTestItemInfo* GetByIndex(int n) { return Pool.GetByIndex(n); }
const ImGuiTestItemInfo* GetByID(ImGuiID id) { return Pool.GetByKey(id); }
// For range-for
size_t size() const { return (size_t)Pool.GetMapSize(); }
const ImGuiTestItemInfo* begin() const { return Pool.Buf.begin(); }
const ImGuiTestItemInfo* end() const { return Pool.Buf.end(); }
const ImGuiTestItemInfo* operator[] (size_t n) { return &Pool.Buf[(int)n]; }
};
//-------------------------------------------------------------------------
// ImGuiTestLog: store textual output of one given Test.
//-------------------------------------------------------------------------
struct IMGUI_API ImGuiTestLogLineInfo
{
ImGuiTestVerboseLevel Level;
int LineOffset;
};
struct IMGUI_API ImGuiTestLog
{
ImGuiTextBuffer Buffer;
ImVector<ImGuiTestLogLineInfo> LineInfo;
int CountPerLevel[ImGuiTestVerboseLevel_COUNT] = {};
// Functions
ImGuiTestLog() {}
bool IsEmpty() const { return Buffer.empty(); }
void Clear();
// Extract log contents filtered per log-level.
// Output:
// - If 'buffer != NULL': all extracted lines are appended to 'buffer'. Use 'buffer->c_str()' on your side to obtain the text.
// - Return value: number of lines extracted (should be equivalent to number of '\n' inside buffer->c_str()).
// - You may call the function with buffer == NULL to only obtain a count without getting the data.
// Verbose levels are inclusive:
// - To get ONLY Error: Use level_min == ImGuiTestVerboseLevel_Error, level_max = ImGuiTestVerboseLevel_Error
// - To get ONLY Error and Warnings: Use level_min == ImGuiTestVerboseLevel_Error, level_max = ImGuiTestVerboseLevel_Warning
// - To get All Errors, Warnings, Debug... Use level_min == ImGuiTestVerboseLevel_Error, level_max = ImGuiTestVerboseLevel_Trace
int ExtractLinesForVerboseLevels(ImGuiTestVerboseLevel level_min, ImGuiTestVerboseLevel level_max, ImGuiTextBuffer* out_buffer);
// [Internal]
void UpdateLineOffsets(ImGuiTestEngineIO* engine_io, ImGuiTestVerboseLevel level, const char* start);
};
//-------------------------------------------------------------------------
// ImGuiTest
//-------------------------------------------------------------------------
typedef void (ImGuiTestGuiFunc)(ImGuiTestContext* ctx);
typedef void (ImGuiTestTestFunc)(ImGuiTestContext* ctx);
// Wraps a placement new of a given type (where 'buffer' is the allocated memory)
typedef void (ImGuiTestVarsConstructor)(void* buffer);
typedef void (ImGuiTestVarsPostConstructor)(ImGuiTestContext* ctx, void* ptr, void* fn);
typedef void (ImGuiTestVarsDestructor)(void* ptr);
// Storage for the output of a test run
struct IMGUI_API ImGuiTestOutput
{
ImGuiTestStatus Status = ImGuiTestStatus_Unknown;
ImGuiTestLog Log;
ImU64 StartTime = 0;
ImU64 EndTime = 0;
};
// Storage for one test
struct IMGUI_API ImGuiTest
{
// Test Definition
const char* Category = NULL; // Literal, not owned
const char* Name = NULL; // Literal, generally not owned unless NameOwned=true
ImGuiTestGroup Group = ImGuiTestGroup_Unknown; // Coarse groups: 'Tests' or 'Perf'
bool NameOwned = false; //
const char* SourceFile = NULL; // __FILE__
int SourceLine = 0; // __LINE__
int SourceLineEnd = 0; // Calculated by ImGuiTestEngine_StartCalcSourceLineEnds()
int ArgVariant = 0; // User parameter. Generally we use it to run variations of a same test by sharing GuiFunc/TestFunc
ImGuiTestFlags Flags = ImGuiTestFlags_None; // See ImGuiTestFlags_
ImFuncPtr(ImGuiTestGuiFunc) GuiFunc = NULL; // GUI function (optional if your test are running over an existing GUI application)
ImFuncPtr(ImGuiTestTestFunc) TestFunc = NULL; // Test function
void* UserData = NULL; // General purpose user data (if assigning capturing lambdas on GuiFunc/TestFunc you may not need to use this)
//ImVector<ImGuiTestRunTask> Dependencies; // Registered via AddDependencyTest(), ran automatically before our test. This is a simpler wrapper to calling ctx->RunChildTest()
// Last Test Output/Status
// (this is the only part that may change after registration)
ImGuiTestOutput Output;
// User variables (which are instantiated when running the test)
// Setup after test registration with SetVarsDataType<>(), access instance during test with GetVars<>().
// This is mostly useful to communicate between GuiFunc and TestFunc. If you don't use both you may not want to use it!
size_t VarsSize = 0;
ImGuiTestVarsConstructor* VarsConstructor = NULL;
ImGuiTestVarsPostConstructor* VarsPostConstructor = NULL; // To override constructor default (in case the default are problematic on the first GuiFunc frame)
void* VarsPostConstructorUserFn = NULL;
ImGuiTestVarsDestructor* VarsDestructor = NULL;
// Functions
ImGuiTest() {}
~ImGuiTest();
void SetOwnedName(const char* name);
template <typename T>
void SetVarsDataType(void(*post_initialize)(ImGuiTestContext* ctx, T& vars) = NULL)
{
VarsSize = sizeof(T);
VarsConstructor = [](void* ptr) { IM_PLACEMENT_NEW(ptr) T; };
VarsDestructor = [](void* ptr) { IM_UNUSED(ptr); reinterpret_cast<T*>(ptr)->~T(); };
if (post_initialize != NULL)
{
VarsPostConstructorUserFn = (void*)post_initialize;
VarsPostConstructor = [](ImGuiTestContext* ctx, void* ptr, void* fn) { ((void (*)(ImGuiTestContext*, T&))(fn))(ctx, *(T*)ptr); };
}
}
};
// Stored in test queue
struct IMGUI_API ImGuiTestRunTask
{
ImGuiTest* Test = NULL;
ImGuiTestRunFlags RunFlags = ImGuiTestRunFlags_None;
};
//-------------------------------------------------------------------------

View File

@@ -0,0 +1,303 @@
// dear imgui test engine
// (result exporters)
// Read https://github.com/ocornut/imgui_test_engine/wiki/Exporting-Results
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include "imgui_te_exporters.h"
#include "imgui_te_engine.h"
#include "imgui_te_internal.h"
#include "thirdparty/Str/Str.h"
//-------------------------------------------------------------------------
// [SECTION] FORWARD DECLARATIONS
//-------------------------------------------------------------------------
static void ImGuiTestEngine_ExportJUnitXml(ImGuiTestEngine* engine, const char* output_file);
//-------------------------------------------------------------------------
// [SECTION] TEST ENGINE EXPORTER FUNCTIONS
//-------------------------------------------------------------------------
// - ImGuiTestEngine_PrintResultSummary()
// - ImGuiTestEngine_Export()
// - ImGuiTestEngine_ExportEx()
// - ImGuiTestEngine_ExportJUnitXml()
//-------------------------------------------------------------------------
void ImGuiTestEngine_PrintResultSummary(ImGuiTestEngine* engine)
{
int count_tested = 0;
int count_success = 0;
ImGuiTestEngine_GetResult(engine, count_tested, count_success);
if (count_success < count_tested)
{
printf("\nFailing tests:\n");
for (ImGuiTest* test : engine->TestsAll)
if (test->Output.Status == ImGuiTestStatus_Error)
printf("- %s\n", test->Name);
}
ImOsConsoleSetTextColor(ImOsConsoleStream_StandardOutput, (count_success == count_tested) ? ImOsConsoleTextColor_BrightGreen : ImOsConsoleTextColor_BrightRed);
printf("\nTests Result: %s\n", (count_success == count_tested) ? "OK" : "Errors");
printf("(%d/%d tests passed)\n", count_success, count_tested);
ImOsConsoleSetTextColor(ImOsConsoleStream_StandardOutput, ImOsConsoleTextColor_White);
}
// This is mostly a copy of ImGuiTestEngine_PrintResultSummary with few additions.
static void ImGuiTestEngine_ExportResultSummary(ImGuiTestEngine* engine, FILE* fp, int indent_count, ImGuiTestGroup group)
{
int count_tested = 0;
int count_success = 0;
for (ImGuiTest* test : engine->TestsAll)
{
if (test->Group != group)
continue;
if (test->Output.Status != ImGuiTestStatus_Unknown)
count_tested++;
if (test->Output.Status == ImGuiTestStatus_Success)
count_success++;
}
Str64 indent_str;
indent_str.reserve(indent_count + 1);
memset(indent_str.c_str(), ' ', indent_count);
indent_str[indent_count] = 0;
const char* indent = indent_str.c_str();
if (count_success < count_tested)
{
fprintf(fp, "\n%sFailing tests:\n", indent);
for (ImGuiTest* test : engine->TestsAll)
{
if (test->Group != group)
continue;
if (test->Output.Status == ImGuiTestStatus_Error)
fprintf(fp, "%s- %s\n", indent, test->Name);
}
fprintf(fp, "\n");
}
fprintf(fp, "%sTests Result: %s\n", indent, (count_success == count_tested) ? "OK" : "Errors");
fprintf(fp, "%s(%d/%d tests passed)\n", indent, count_success, count_tested);
}
static bool ImGuiTestEngine_HasAnyLogLines(ImGuiTestLog* test_log, ImGuiTestVerboseLevel level)
{
for (auto& line_info : test_log->LineInfo)
if (line_info.Level <= level)
return true;
return false;
}
static void ImGuiTestEngine_PrintLogLines(FILE* fp, ImGuiTestLog* test_log, int indent, ImGuiTestVerboseLevel level)
{
Str128 log_line;
for (auto& line_info : test_log->LineInfo)
{
if (line_info.Level > level)
continue;
const char* line_start = test_log->Buffer.c_str() + line_info.LineOffset;
const char* line_end = strstr(line_start, "\n"); // FIXME: Incorrect.
log_line.set(line_start, line_end);
ImStrXmlEscape(&log_line); // FIXME: Should not be here considering the function name.
// Some users may want to disable indenting?
fprintf(fp, "%*s%s\n", indent, "", log_line.c_str());
}
}
// Export using settings stored in ImGuiTestEngineIO
// This is called by ImGuiTestEngine_CrashHandler().
void ImGuiTestEngine_Export(ImGuiTestEngine* engine)
{
ImGuiTestEngineIO& io = engine->IO;
ImGuiTestEngine_ExportEx(engine, io.ExportResultsFormat, io.ExportResultsFilename);
}
// Export using custom settings.
void ImGuiTestEngine_ExportEx(ImGuiTestEngine* engine, ImGuiTestEngineExportFormat format, const char* filename)
{
if (format == ImGuiTestEngineExportFormat_None)
return;
IM_ASSERT(filename != NULL);
if (format == ImGuiTestEngineExportFormat_JUnitXml)
ImGuiTestEngine_ExportJUnitXml(engine, filename);
else
IM_ASSERT(0);
}
void ImGuiTestEngine_ExportJUnitXml(ImGuiTestEngine* engine, const char* output_file)
{
IM_ASSERT(engine != NULL);
IM_ASSERT(output_file != NULL);
FILE* fp = fopen(output_file, "w+b");
if (fp == NULL)
{
fprintf(stderr, "Writing '%s' failed.\n", output_file);
return;
}
// Per-testsuite test statistics.
struct
{
const char* Name = NULL;
int Tests = 0;
int Failures = 0;
int Disabled = 0;
} testsuites[ImGuiTestGroup_COUNT];
testsuites[ImGuiTestGroup_Tests].Name = "tests";
testsuites[ImGuiTestGroup_Perfs].Name = "perfs";
for (int n = 0; n < engine->TestsAll.Size; n++)
{
ImGuiTest* test = engine->TestsAll[n];
auto* stats = &testsuites[test->Group];
stats->Tests += 1;
if (test->Output.Status == ImGuiTestStatus_Error)
stats->Failures += 1;
else if (test->Output.Status == ImGuiTestStatus_Unknown)
stats->Disabled += 1;
}
// Attributes for <testsuites> tag.
const char* testsuites_name = "Dear ImGui";
int testsuites_failures = 0;
int testsuites_tests = 0;
int testsuites_disabled = 0;
float testsuites_time = (float)((double)(engine->BatchEndTime - engine->BatchStartTime) / 1000000.0);
for (int testsuite_id = ImGuiTestGroup_Tests; testsuite_id < ImGuiTestGroup_COUNT; testsuite_id++)
{
testsuites_tests += testsuites[testsuite_id].Tests;
testsuites_failures += testsuites[testsuite_id].Failures;
testsuites_disabled += testsuites[testsuite_id].Disabled;
}
// FIXME: "errors" attribute and <error> tag in <testcase> may be supported if we have means to catch unexpected errors like assertions.
fprintf(fp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<testsuites disabled=\"%d\" errors=\"0\" failures=\"%d\" name=\"%s\" tests=\"%d\" time=\"%.3f\">\n",
testsuites_disabled, testsuites_failures, testsuites_name, testsuites_tests, testsuites_time);
for (int testsuite_id = ImGuiTestGroup_Tests; testsuite_id < ImGuiTestGroup_COUNT; testsuite_id++)
{
// Attributes for <testsuite> tag.
auto* testsuite = &testsuites[testsuite_id];
float testsuite_time = testsuites_time; // FIXME: We do not differentiate between tests and perfs, they are executed in one big batch.
Str30 testsuite_timestamp = "";
ImTimestampToISO8601(engine->BatchStartTime, &testsuite_timestamp);
fprintf(fp, " <testsuite name=\"%s\" tests=\"%d\" disabled=\"%d\" errors=\"0\" failures=\"%d\" hostname=\"\" id=\"%d\" package=\"\" skipped=\"0\" time=\"%.3f\" timestamp=\"%s\">\n",
testsuite->Name, testsuite->Tests, testsuite->Disabled, testsuite->Failures, testsuite_id, testsuite_time, testsuite_timestamp.c_str());
for (int n = 0; n < engine->TestsAll.Size; n++)
{
ImGuiTest* test = engine->TestsAll[n];
if (test->Group != testsuite_id)
continue;
ImGuiTestOutput* test_output = &test->Output;
ImGuiTestLog* test_log = &test_output->Log;
// Attributes for <testcase> tag.
const char* testcase_name = test->Name;
const char* testcase_classname = test->Category;
const char* testcase_status = ImGuiTestEngine_GetStatusName(test_output->Status);
const float testcase_time = (float)((double)(test_output->EndTime - test_output->StartTime) / 1000000.0);
fprintf(fp, " <testcase name=\"%s\" assertions=\"0\" classname=\"%s\" status=\"%s\" time=\"%.3f\">\n",
testcase_name, testcase_classname, testcase_status, testcase_time);
if (test_output->Status == ImGuiTestStatus_Error)
{
// Skip last error message because it is generic information that test failed.
Str128 log_line;
for (int i = test_log->LineInfo.Size - 2; i >= 0; i--)
{
ImGuiTestLogLineInfo* line_info = &test_log->LineInfo[i];
if (line_info->Level > engine->IO.ConfigVerboseLevelOnError)
continue;
if (line_info->Level == ImGuiTestVerboseLevel_Error)
{
const char* line_start = test_log->Buffer.c_str() + line_info->LineOffset;
const char* line_end = strstr(line_start, "\n");
log_line.set(line_start, line_end);
ImStrXmlEscape(&log_line);
break;
}
}
// Failing tests save their "on error" log output in text element of <failure> tag.
fprintf(fp, " <failure message=\"%s\" type=\"error\">\n", log_line.c_str());
ImGuiTestEngine_PrintLogLines(fp, test_log, 8, engine->IO.ConfigVerboseLevelOnError);
fprintf(fp, " </failure>\n");
}
if (test_output->Status == ImGuiTestStatus_Unknown)
{
fprintf(fp, " <skipped message=\"Skipped\" />\n");
}
else
{
// Succeeding tests save their default log output output as "stdout".
if (ImGuiTestEngine_HasAnyLogLines(test_log, engine->IO.ConfigVerboseLevel))
{
fprintf(fp, " <system-out>\n");
ImGuiTestEngine_PrintLogLines(fp, test_log, 8, engine->IO.ConfigVerboseLevel);
fprintf(fp, " </system-out>\n");
}
// Save error messages as "stderr".
if (ImGuiTestEngine_HasAnyLogLines(test_log, ImGuiTestVerboseLevel_Error))
{
fprintf(fp, " <system-err>\n");
ImGuiTestEngine_PrintLogLines(fp, test_log, 8, ImGuiTestVerboseLevel_Error);
fprintf(fp, " </system-err>\n");
}
}
fprintf(fp, " </testcase>\n");
}
if (testsuites[testsuite_id].Disabled < testsuites[testsuite_id].Tests) // Any tests executed
{
// Log all log messages as "stdout".
fprintf(fp, " <system-out>\n");
for (int n = 0; n < engine->TestsAll.Size; n++)
{
ImGuiTest* test = engine->TestsAll[n];
ImGuiTestOutput* test_output = &test->Output;
if (test->Group != testsuite_id)
continue;
if (test_output->Status == ImGuiTestStatus_Unknown)
continue;
fprintf(fp, " [0000] Test: '%s' '%s'..\n", test->Category, test->Name);
ImGuiTestVerboseLevel level = test_output->Status == ImGuiTestStatus_Error ? engine->IO.ConfigVerboseLevelOnError : engine->IO.ConfigVerboseLevel;
ImGuiTestEngine_PrintLogLines(fp, &test_output->Log, 6, level);
}
ImGuiTestEngine_ExportResultSummary(engine, fp, 6, (ImGuiTestGroup)testsuite_id);
fprintf(fp, " </system-out>\n");
// Log all warning and error messages as "stderr".
fprintf(fp, " <system-err>\n");
for (int n = 0; n < engine->TestsAll.Size; n++)
{
ImGuiTest* test = engine->TestsAll[n];
ImGuiTestOutput* test_output = &test->Output;
if (test->Group != testsuite_id)
continue;
if (test_output->Status == ImGuiTestStatus_Unknown)
continue;
fprintf(fp, " [0000] Test: '%s' '%s'..\n", test->Category, test->Name);
ImGuiTestEngine_PrintLogLines(fp, &test_output->Log, 6, ImGuiTestVerboseLevel_Warning);
}
ImGuiTestEngine_ExportResultSummary(engine, fp, 6, (ImGuiTestGroup)testsuite_id);
fprintf(fp, " </system-err>\n");
}
fprintf(fp, " </testsuite>\n");
}
fprintf(fp, "</testsuites>\n");
fclose(fp);
fprintf(stdout, "Saved test results to '%s' successfully.\n", output_file);
}

View File

@@ -0,0 +1,59 @@
// dear imgui test engine
// (result exporters)
// Read https://github.com/ocornut/imgui_test_engine/wiki/Exporting-Results
#pragma once
//-------------------------------------------------------------------------
// Description
//-------------------------------------------------------------------------
//
// Test results may be exported in one of supported formats.
// To enable result exporting please configure test engine as follows:
//
// ImGuiTestEngineIO& test_io = ImGuiTestEngine_GetIO(engine);
// test_io.ExportResultsFile = "output_file.xml";
// test_io.ExportResultsFormat = ImGuiTestEngineExportFormat_<...>;
//
// JUnit XML format
//------------------
// JUnit XML format described at https://llg.cubic.org/docs/junit/. Many
// third party applications support consumption of this format. Some of
// of them are listed here:
// - Jenkins
// - Installation guide: https://www.jenkins.io/doc/book/installing/docker/
// - JUnit plugin: https://plugins.jenkins.io/junit/
// - xunit-viewer
// - Project: https://github.com/lukejpreston/xunit-viewer
// - Install npm: https://docs.npmjs.com/downloading-and-installing-node-js-and-npm
// - Install viewer and view test results:
// npm install xunit-viewer
// imgui_test_suite -nopause -v2 -ve4 -nogui -export-file junit.xml tests
// node_modules/xunit-viewer/bin/xunit-viewer -r junit.xml -o junit.html
// - Open junit.html
//
//-------------------------------------------------------------------------
// Forward Declarations
//-------------------------------------------------------------------------
struct ImGuiTestEngine;
//-------------------------------------------------------------------------
// Types
//-------------------------------------------------------------------------
enum ImGuiTestEngineExportFormat : int
{
ImGuiTestEngineExportFormat_None = 0,
ImGuiTestEngineExportFormat_JUnitXml,
};
//-------------------------------------------------------------------------
// Functions
//-------------------------------------------------------------------------
void ImGuiTestEngine_PrintResultSummary(ImGuiTestEngine* engine);
void ImGuiTestEngine_Export(ImGuiTestEngine* engine);
void ImGuiTestEngine_ExportEx(ImGuiTestEngine* engine, ImGuiTestEngineExportFormat format, const char* filename);

View File

@@ -0,0 +1,57 @@
// dear imgui test engine
// (template for compile-time configuration)
// Replicate or #include this file in your imconfig.h to enable test engine.
// Compile Dear ImGui with test engine hooks
// (Important: This is a value-less define, to be consistent with other defines used in core dear imgui.)
#define IMGUI_ENABLE_TEST_ENGINE
// [Optional, default 0] Enable plotting of perflog data for comparing performance of different runs.
// This feature requires ImPlot to be linked in the application.
#ifndef IMGUI_TEST_ENGINE_ENABLE_IMPLOT
#define IMGUI_TEST_ENGINE_ENABLE_IMPLOT 0
#endif
// [Optional, default 1] Enable screen capture and PNG/GIF saving functionalities
// There's not much point to disable this but we provide it to reassure user that the dependencies on imstb_image_write.h and ffmpeg are technically optional.
#ifndef IMGUI_TEST_ENGINE_ENABLE_CAPTURE
#define IMGUI_TEST_ENGINE_ENABLE_CAPTURE 1
#endif
// [Optional, default 0] Using std::function and <functional> for function pointers such as ImGuiTest::TestFunc and ImGuiTest::GuiFunc
#ifndef IMGUI_TEST_ENGINE_ENABLE_STD_FUNCTION
#define IMGUI_TEST_ENGINE_ENABLE_STD_FUNCTION 0
#endif
// [Optional, default 0] Automatically fill ImGuiTestEngineIO::CoroutineFuncs with a default implementation using std::thread
#ifndef IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL
#define IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL 0
#endif
// Define IM_DEBUG_BREAK macros so it is accessible in imgui.h
// (this is a conveniance for app using test engine may define an IM_ASSERT() that uses this instead of an actual assert)
// (this is a copy of the block in imgui_internal.h. if the one in imgui_internal.h were to be defined at the top of imgui.h we wouldn't need this)
#ifndef IM_DEBUG_BREAK
#if defined (_MSC_VER)
#define IM_DEBUG_BREAK() __debugbreak()
#elif defined(__clang__)
#define IM_DEBUG_BREAK() __builtin_debugtrap()
#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
#define IM_DEBUG_BREAK() __asm__ volatile("int $0x03")
#elif defined(__GNUC__) && defined(__thumb__)
#define IM_DEBUG_BREAK() __asm__ volatile(".inst 0xde01")
#elif defined(__GNUC__) && defined(__arm__) && !defined(__thumb__)
#define IM_DEBUG_BREAK() __asm__ volatile(".inst 0xe7f001f0");
#else
#define IM_DEBUG_BREAK() IM_ASSERT(0) // It is expected that you define IM_DEBUG_BREAK() into something that will break nicely in a debugger!
#endif
#endif // #ifndef IMGUI_DEBUG_BREAK
// [Options] We provide custom assert macro used by our our test suite, which you may use:
// - Calling IM_DEBUG_BREAK() instead of an actual assert, so we can easily recover and step over (compared to many assert implementations).
// - If a test is running, test name will be included in the log.
// - Macro is calling IM_DEBUG_BREAK() inline to get debugger to break in the calling function (instead of a deeper callstack level).
// - Macro is using comma operator instead of an if() to avoid "conditional expression is constant" warnings.
extern void ImGuiTestEngine_AssertLog(const char* expr, const char* file, const char* func, int line);
#define IM_TEST_ENGINE_ASSERT(_EXPR) do { if ((void)0, !(_EXPR)) { ImGuiTestEngine_AssertLog(#_EXPR, __FILE__, __func__, __LINE__); IM_DEBUG_BREAK(); } } while (0)
// V_ASSERT_CONTRACT, assertMacro:IM_ASSERT

View File

@@ -0,0 +1,213 @@
// dear imgui test engine
// (internal api)
#pragma once
#include "imgui_te_coroutine.h"
#include "imgui_te_utils.h" // ImMovingAverage
#include "imgui_capture_tool.h" // ImGuiCaptureTool // FIXME
//-------------------------------------------------------------------------
// FORWARD DECLARATIONS
//-------------------------------------------------------------------------
class Str; // Str<> from thirdparty/Str/Str.h
struct ImGuiPerfTool;
//-------------------------------------------------------------------------
// DATA STRUCTURES
//-------------------------------------------------------------------------
// Query item position/window/state given ID.
struct ImGuiTestInfoTask
{
// Input
ImGuiID ID = 0;
int FrameCount = -1; // Timestamp of request
char DebugName[64] = ""; // Debug string representing the queried ID
// Output
ImGuiTestItemInfo Result;
};
// Gather item list in given parent ID.
struct ImGuiTestGatherTask
{
// Input
ImGuiID InParentID = 0;
int InMaxDepth = 0;
short InLayerMask = 0;
// Output/Temp
ImGuiTestItemList* OutList = NULL;
ImGuiTestItemInfo* LastItemInfo = NULL;
void Clear() { memset(this, 0, sizeof(*this)); }
};
// Find item ID given a label and a parent id
// Usually used by queries with wildcards such as ItemInfo("hello/**/foo/bar")
struct ImGuiTestFindByLabelTask
{
// Input
ImGuiID InPrefixId = 0; // A known base ID which appears BEFORE the wildcard ID (for "hello/**/foo/bar" it would be hash of "hello")
int InSuffixDepth = 0; // Number of labels in a path, after unknown base ID (for "hello/**/foo/bar" it would be 2)
const char* InSuffix = NULL; // A label string which appears on ID stack after unknown base ID (for "hello/**/foo/bar" it would be "foo/bar")
const char* InSuffixLastItem = NULL; // A last label string (for "hello/**/foo/bar" it would be "bar")
ImGuiID InSuffixLastItemHash = 0;
ImGuiItemStatusFlags InFilterItemStatusFlags = 0; // Flags required for item to be returned
// Output
ImGuiID OutItemId = 0; // Result item ID
};
enum ImGuiTestInputType
{
ImGuiTestInputType_None,
ImGuiTestInputType_Key,
ImGuiTestInputType_Char,
ImGuiTestInputType_ViewportFocus,
ImGuiTestInputType_ViewportClose
};
// FIXME: May want to strip further now that core imgui is using its own input queue
struct ImGuiTestInput
{
ImGuiTestInputType Type = ImGuiTestInputType_None;
ImGuiKeyChord KeyChord = ImGuiKey_None;
ImWchar Char = 0;
bool Down = false;
ImGuiID ViewportId = 0;
static ImGuiTestInput ForKeyChord(ImGuiKeyChord key_chord, bool down)
{
ImGuiTestInput inp;
inp.Type = ImGuiTestInputType_Key;
inp.KeyChord = key_chord;
inp.Down = down;
return inp;
}
static ImGuiTestInput ForChar(ImWchar v)
{
ImGuiTestInput inp;
inp.Type = ImGuiTestInputType_Char;
inp.Char = v;
return inp;
}
static ImGuiTestInput ForViewportFocus(ImGuiID viewport_id)
{
ImGuiTestInput inp;
inp.Type = ImGuiTestInputType_ViewportFocus;
inp.ViewportId = viewport_id;
return inp;
}
static ImGuiTestInput ForViewportClose(ImGuiID viewport_id)
{
ImGuiTestInput inp;
inp.Type = ImGuiTestInputType_ViewportClose;
inp.ViewportId = viewport_id;
return inp;
}
};
struct ImGuiTestInputs
{
ImVec2 MousePosValue; // Own non-rounded copy of MousePos in order facilitate simulating mouse movement very slow speed and high-framerate
ImVec2 MouseWheel;
ImGuiID MouseHoveredViewport = 0;
int MouseButtonsValue = 0x00; // FIXME-TESTS: Use simulated_io.MouseDown[] ?
ImVector<ImGuiTestInput> Queue;
bool HostEscDown = false;
float HostEscDownDuration = -1.0f; // Maintain our own DownDuration for host/backend ESC key so we can abort.
};
// [Internal] Test Engine Context
struct ImGuiTestEngine
{
ImGuiTestEngineIO IO;
ImGuiContext* UiContextTarget = NULL; // imgui context for testing
ImGuiContext* UiContextActive = NULL; // imgui context for testing == UiContextTarget or NULL
bool Started = false;
ImU64 BatchStartTime = 0;
ImU64 BatchEndTime = 0;
int FrameCount = 0;
float OverrideDeltaTime = -1.0f; // Inject custom delta time into imgui context to simulate clock passing faster than wall clock time.
ImVector<ImGuiTest*> TestsAll;
ImVector<ImGuiTestRunTask> TestsQueue;
ImGuiTestContext* TestContext = NULL;
ImVector<ImGuiTestInfoTask*>InfoTasks;
ImGuiTestGatherTask GatherTask;
ImGuiTestFindByLabelTask FindByLabelTask;
ImGuiTestCoroutineHandle TestQueueCoroutine = NULL; // Coroutine to run the test queue
bool TestQueueCoroutineShouldExit = false; // Flag to indicate that we are shutting down and the test queue coroutine should stop
// Inputs
ImGuiTestInputs Inputs;
// UI support
bool Abort = false;
ImGuiTest* UiSelectAndScrollToTest = NULL;
ImGuiTest* UiSelectedTest = NULL;
Str* UiFilterTests;
Str* UiFilterPerfs;
ImU32 UiFilterByStatusMask = ~0u;
bool UiMetricsOpen = false;
bool UiDebugLogOpen = false;
bool UiCaptureToolOpen = false;
bool UiStackToolOpen = false;
bool UiPerfToolOpen = false;
float UiLogHeight = 150.0f;
// Performance Monitor
double PerfRefDeltaTime;
ImMovingAverage<double> PerfDeltaTime100;
ImMovingAverage<double> PerfDeltaTime500;
ImGuiPerfTool* PerfTool = NULL;
// Screen/Video Capturing
ImGuiCaptureToolUI CaptureTool; // Capture tool UI
ImGuiCaptureContext CaptureContext; // Capture context used in tests
ImGuiCaptureArgs* CaptureCurrentArgs = NULL;
// Tools
bool PostSwapCalled = false;
bool ToolDebugRebootUiContext = false; // Completely shutdown and recreate the dear imgui context in place
bool ToolSlowDown = false;
int ToolSlowDownMs = 100;
ImGuiTestRunSpeed BackupConfigRunSpeed = ImGuiTestRunSpeed_Fast;
bool BackupConfigNoThrottle = false;
// Functions
ImGuiTestEngine();
~ImGuiTestEngine();
};
//-------------------------------------------------------------------------
// INTERNAL FUNCTIONS
//-------------------------------------------------------------------------
ImGuiTestItemInfo* ImGuiTestEngine_FindItemInfo(ImGuiTestEngine* engine, ImGuiID id, const char* debug_id);
void ImGuiTestEngine_Yield(ImGuiTestEngine* engine);
void ImGuiTestEngine_SetDeltaTime(ImGuiTestEngine* engine, float delta_time);
int ImGuiTestEngine_GetFrameCount(ImGuiTestEngine* engine);
bool ImGuiTestEngine_PassFilter(ImGuiTest* test, const char* filter);
void ImGuiTestEngine_RunTest(ImGuiTestEngine* engine, ImGuiTestContext* ctx, ImGuiTest* test, ImGuiTestRunFlags run_flags);
void ImGuiTestEngine_RebootUiContext(ImGuiTestEngine* engine);
ImGuiPerfTool* ImGuiTestEngine_GetPerfTool(ImGuiTestEngine* engine);
// Screen/Video Capturing
bool ImGuiTestEngine_CaptureScreenshot(ImGuiTestEngine* engine, ImGuiCaptureArgs* args);
bool ImGuiTestEngine_CaptureBeginVideo(ImGuiTestEngine* engine, ImGuiCaptureArgs* args);
bool ImGuiTestEngine_CaptureEndVideo(ImGuiTestEngine* engine, ImGuiCaptureArgs* args);
// Helper functions
const char* ImGuiTestEngine_GetStatusName(ImGuiTestStatus v);
const char* ImGuiTestEngine_GetRunSpeedName(ImGuiTestRunSpeed v);
const char* ImGuiTestEngine_GetVerboseLevelName(ImGuiTestVerboseLevel v);
//-------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,131 @@
// dear imgui test engine
// (performance tool)
// Browse and visualize samples recorded by ctx->PerfCapture() calls.
// User access via 'Test Engine UI -> Tools -> Perf Tool'
#pragma once
#include "imgui.h"
// Forward Declaration
struct ImGuiPerfToolColumnInfo;
struct ImGuiTestEngine;
struct ImGuiCsvParser;
// Configuration
#define IMGUI_PERFLOG_DEFAULT_FILENAME "output/imgui_perflog.csv"
// [Internal] Perf log entry. Changes to this struct should be reflected in ImGuiTestContext::PerfCapture() and ImGuiTestEngine_Start().
// This struct assumes strings stored here will be available until next ImGuiPerfTool::Clear() call. Fortunately we do not have to actively
// manage lifetime of these strings. New entries are created only in two cases:
// 1. ImGuiTestEngine_PerfToolAppendToCSV() call after perf test has run. This call receives ImGuiPerfToolEntry with const strings stored indefinitely by application.
// 2. As a consequence of ImGuiPerfTool::LoadCSV() call, we persist the ImGuiCSVParser instance, which keeps parsed CSV text, from which strings are referenced.
// As a result our solution also doesn't make many allocations.
struct IMGUI_API ImGuiPerfToolEntry
{
ImU64 Timestamp = 0; // Title of a particular batch of perftool entries.
const char* Category = NULL; // Name of category perf test is in.
const char* TestName = NULL; // Name of perf test.
double DtDeltaMs = 0.0; // Result of perf test.
double DtDeltaMsMin = +FLT_MAX; // May be used by perftool.
double DtDeltaMsMax = -FLT_MAX; // May be used by perftool.
int NumSamples = 1; // Number aggregated samples.
int PerfStressAmount = 0; //
const char* GitBranchName = NULL; // Build information.
const char* BuildType = NULL; //
const char* Cpu = NULL; //
const char* OS = NULL; //
const char* Compiler = NULL; //
const char* Date = NULL; // Date of this entry or min date of combined entries.
//const char* DateMax = NULL; // Max date of combined entries, or NULL.
double VsBaseline = 0.0; // Percent difference vs baseline.
int LabelIndex = 0; // Index of TestName in ImGuiPerfTool::_LabelsVisible.
ImGuiPerfToolEntry() { }
ImGuiPerfToolEntry(const ImGuiPerfToolEntry& rhs) { Set(rhs); }
ImGuiPerfToolEntry& operator=(const ImGuiPerfToolEntry& rhs){ Set(rhs); return *this; }
void Set(const ImGuiPerfToolEntry& rhs);
};
// [Internal] Perf log batch.
struct ImGuiPerfToolBatch
{
ImU64 BatchID = 0; // Timestamp of the batch, or unique ID of the build in combined mode.
int NumSamples = 0; // A number of unique batches aggregated.
int BranchIndex = 0; // For per-branch color mapping.
ImVector<ImGuiPerfToolEntry> Entries; // Aggregated perf test entries. Order follows ImGuiPerfTool::_LabelsVisible order.
~ImGuiPerfToolBatch() { Entries.clear_destruct(); } // FIXME: Misleading: nothing to destruct in that struct?
};
enum ImGuiPerfToolDisplayType : int
{
ImGuiPerfToolDisplayType_Simple, // Each run will be displayed individually.
ImGuiPerfToolDisplayType_PerBranchColors, // Use one bar color per branch.
ImGuiPerfToolDisplayType_CombineByBuildInfo, // Entries with same build information will be averaged.
};
//
struct IMGUI_API ImGuiPerfTool
{
ImVector<ImGuiPerfToolEntry> _SrcData; // Raw entries from CSV file (with string pointer into CSV data).
ImVector<const char*> _Labels;
ImVector<const char*> _LabelsVisible; // ImPlot requires a pointer of all labels beforehand. Always contains a dummy "" entry at the end!
ImVector<ImGuiPerfToolBatch> _Batches;
ImGuiStorage _LabelBarCounts; // Number bars each label will render.
int _NumVisibleBuilds = 0; // Cached number of visible builds.
int _NumUniqueBuilds = 0; // Cached number of unique builds.
ImGuiPerfToolDisplayType _DisplayType = ImGuiPerfToolDisplayType_CombineByBuildInfo;
int _BaselineBatchIndex = 0; // Index of baseline build.
ImU64 _BaselineTimestamp = 0;
ImU64 _BaselineBuildId = 0;
char _Filter[128]; // Context menu filtering substring.
char _FilterDateFrom[11] = {};
char _FilterDateTo[11] = {};
float _InfoTableHeight = 180.0f;
int _AlignStress = 0; // Alignment values for build info components, so they look aligned in the legend.
int _AlignType = 0;
int _AlignOs = 0;
int _AlignCpu = 0;
int _AlignCompiler = 0;
int _AlignBranch = 0;
int _AlignSamples = 0;
bool _InfoTableSortDirty = false;
ImVector<ImU64> _InfoTableSort; // _InfoTableSort[_LabelsVisible.Size * _Batches.Size]. Contains sorted batch indices for each label.
const ImGuiTableSortSpecs* _InfoTableSortSpecs = NULL; // Current table sort specs.
ImGuiStorage _TempSet; // Used as a set
int _TableHoveredTest = -1; // Index within _VisibleLabelPointers array.
int _TableHoveredBatch = -1;
int _PlotHoverTest = -1;
int _PlotHoverBatch = -1;
bool _PlotHoverTestLabel = false;
bool _ReportGenerating = false;
ImGuiStorage _Visibility;
ImGuiCsvParser* _CsvParser = NULL; // We keep this around and point to its fields
ImGuiPerfTool();
~ImGuiPerfTool();
void Clear();
bool LoadCSV(const char* filename = NULL);
void AddEntry(ImGuiPerfToolEntry* entry);
void ShowPerfToolWindow(ImGuiTestEngine* engine, bool* p_open);
void ViewOnly(const char* perf_name);
void ViewOnly(const char** perf_names);
ImGuiPerfToolEntry* GetEntryByBatchIdx(int idx, const char* perf_name = NULL);
bool SaveHtmlReport(const char* file_name, const char* image_file = NULL);
inline bool Empty() { return _SrcData.empty(); }
void _Rebuild();
bool _IsVisibleBuild(ImGuiPerfToolBatch* batch);
bool _IsVisibleBuild(ImGuiPerfToolEntry* batch);
bool _IsVisibleTest(const char* test_name);
void _CalculateLegendAlignment();
void _ShowEntriesPlot();
void _ShowEntriesTable();
void _SetBaseline(int batch_index);
void _AddSettingsHandler();
void _UnpackSortedKey(ImU64 key, int* batch_index, int* entry_index, int* monotonic_index = NULL);
};
IMGUI_API void ImGuiTestEngine_PerfToolAppendToCSV(ImGuiPerfTool* perf_log, ImGuiPerfToolEntry* entry, const char* filename = NULL);

View File

@@ -0,0 +1,842 @@
// dear imgui test engine
// (ui)
// If you run tests in an interactive or visible application, you may want to call ImGuiTestEngine_ShowTestEngineWindows()
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#define IMGUI_DEFINE_MATH_OPERATORS
#include "imgui_te_ui.h"
#include "imgui.h"
#include "imgui_internal.h"
#include "imgui_te_engine.h"
#include "imgui_te_context.h"
#include "imgui_te_internal.h"
#include "imgui_te_perftool.h"
#include "thirdparty/Str/Str.h"
//-------------------------------------------------------------------------
// TEST ENGINE: USER INTERFACE
//-------------------------------------------------------------------------
// - DrawTestLog() [internal]
// - GetVerboseLevelName() [internal]
// - ShowTestGroup() [internal]
// - ImGuiTestEngine_ShowTestWindows()
//-------------------------------------------------------------------------
// Look for " filename:number " in the string and add menu option to open source.
static bool ParseLineAndDrawFileOpenItemForSourceFile(ImGuiTestEngine* e, ImGuiTest* test, const char* line_start, const char* line_end)
{
const char* separator = ImStrchrRange(line_start, line_end, ':');
if (separator == NULL)
return false;
const char* path_end = separator;
const char* path_begin = separator - 1;
while (path_begin > line_start&& path_begin[-1] != ' ')
path_begin--;
if (path_begin == path_end)
return false;
int line_no = -1;
sscanf(separator + 1, "%d ", &line_no);
if (line_no == -1)
return false;
Str256f buf("Open '%.*s' at line %d", (int)(path_end - path_begin), path_begin, line_no);
if (ImGui::MenuItem(buf.c_str()))
{
// FIXME-TESTS: Assume folder is same as folder of test->SourceFile!
const char* src_path = test->SourceFile;
const char* src_name = ImPathFindFilename(src_path);
buf.setf("%.*s%.*s", (int)(src_name - src_path), src_path, (int)(path_end - path_begin), path_begin);
ImGuiTestEngineIO& e_io = ImGuiTestEngine_GetIO(e);
e_io.SrcFileOpenFunc(buf.c_str(), line_no, e_io.SrcFileOpenUserData);
}
return true;
}
// Look for "[ ,"]filename.png" in the string and add menu option to open image.
static bool ParseLineAndDrawFileOpenItemForImageFile(ImGuiTestEngine* e, ImGuiTest* test, const char* line_start, const char* line_end, const char* file_ext)
{
IM_UNUSED(e);
IM_UNUSED(test);
const char* extension = ImStristr(line_start, line_end, file_ext, NULL);
if (extension == NULL)
return false;
const char* path_end = extension + strlen(file_ext);
const char* path_begin = extension - 1;
while (path_begin > line_start && path_begin[-1] != ' ' && path_begin[-1] != '\'' && path_begin[-1] != '\"')
path_begin--;
if (path_begin == path_end)
return false;
Str256 buf;
// Open file
buf.setf("Open file: %.*s", (int)(path_end - path_begin), path_begin);
if (ImGui::MenuItem(buf.c_str()))
{
buf.setf("%.*s", (int)(path_end - path_begin), path_begin);
ImPathFixSeparatorsForCurrentOS(buf.c_str());
ImOsOpenInShell(buf.c_str());
}
// Open folder
const char* folder_begin = path_begin;
const char* folder_end = ImPathFindFilename(path_begin, path_end);
buf.setf("Open folder: %.*s", (int)(folder_end - folder_begin), path_begin);
if (ImGui::MenuItem(buf.c_str()))
{
buf.setf("%.*s", (int)(folder_end - folder_begin), folder_begin);
ImPathFixSeparatorsForCurrentOS(buf.c_str());
ImOsOpenInShell(buf.c_str());
}
return true;
}
static bool ParseLineAndDrawFileOpenItem(ImGuiTestEngine* e, ImGuiTest* test, const char* line_start, const char* line_end)
{
if (ParseLineAndDrawFileOpenItemForSourceFile(e, test, line_start, line_end))
return true;
if (ParseLineAndDrawFileOpenItemForImageFile(e, test, line_start, line_end, ".png"))
return true;
if (ParseLineAndDrawFileOpenItemForImageFile(e, test, line_start, line_end, ".gif"))
return true;
if (ParseLineAndDrawFileOpenItemForImageFile(e, test, line_start, line_end, ".mp4"))
return true;
return false;
}
static float GetDpiScale()
{
#ifdef IMGUI_HAS_VIEWPORT
return ImGui::GetWindowViewport()->DpiScale;
#else
return 1.0f;
#endif
}
static void DrawTestLog(ImGuiTestEngine* e, ImGuiTest* test)
{
const ImU32 error_col = IM_COL32(255, 150, 150, 255);
const ImU32 warning_col = IM_COL32(240, 240, 150, 255);
const ImU32 unimportant_col = IM_COL32(190, 190, 190, 255);
const float dpi_scale = GetDpiScale();
ImGuiTestOutput* test_output = &test->Output;
ImGuiTestLog* log = &test_output->Log;
const char* text = log->Buffer.begin();
const char* text_end = log->Buffer.end();
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 2.0f) * dpi_scale);
ImGuiListClipper clipper;
ImGuiTestVerboseLevel max_log_level = test_output->Status == ImGuiTestStatus_Error ? e->IO.ConfigVerboseLevelOnError : e->IO.ConfigVerboseLevel;
int line_count = log->ExtractLinesForVerboseLevels(ImGuiTestVerboseLevel_Silent, max_log_level, NULL);
int current_index_clipped = -1;
int current_index_abs = 0;
clipper.Begin(line_count);
while (clipper.Step())
{
for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++)
{
// Advance index_by_log_level to find log entry indicated by line_no.
ImGuiTestLogLineInfo* line_info = NULL;
while (current_index_clipped < line_no)
{
line_info = &log->LineInfo[current_index_abs];
if (line_info->Level <= max_log_level)
current_index_clipped++;
current_index_abs++;
}
const char* line_start = text + line_info->LineOffset;
const char* line_end = strchr(line_start, '\n');
if (line_end == NULL)
line_end = text_end;
switch (line_info->Level)
{
case ImGuiTestVerboseLevel_Error:
ImGui::PushStyleColor(ImGuiCol_Text, error_col);
break;
case ImGuiTestVerboseLevel_Warning:
ImGui::PushStyleColor(ImGuiCol_Text, warning_col);
break;
case ImGuiTestVerboseLevel_Debug:
case ImGuiTestVerboseLevel_Trace:
ImGui::PushStyleColor(ImGuiCol_Text, unimportant_col);
break;
default:
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32_WHITE);
break;
}
ImGui::TextUnformatted(line_start, line_end);
ImGui::PopStyleColor();
ImGui::PushID(line_no);
if (ImGui::BeginPopupContextItem("Context", 1))
{
if (!ParseLineAndDrawFileOpenItem(e, test, line_start, line_end))
ImGui::MenuItem("No options", NULL, false, false);
ImGui::EndPopup();
}
ImGui::PopID();
}
}
ImGui::PopStyleVar();
}
#if IMGUI_VERSION_NUM <= 18963
namespace ImGui
{
void SetItemTooltip(const char* fmt, ...)
{
if (ImGui::IsItemHovered())
{
va_list args;
va_start(args, fmt);
ImGui::SetTooltipV(fmt, args);
va_end(args);
}
}
} // namespace ImGui
#endif
static bool ShowTestGroupFilterTest(ImGuiTestEngine* e, ImGuiTestGroup group, const char* filter, ImGuiTest* test)
{
if (test->Group != group)
return false;
if (!ImGuiTestEngine_PassFilter(test, *filter ? filter : "all"))
return false;
if ((e->UiFilterByStatusMask & (1 << test->Output.Status)) == 0)
return false;
return true;
}
static void GetFailingTestsAsString(ImGuiTestEngine* e, ImGuiTestGroup group, char separator, Str* out_string)
{
IM_ASSERT(out_string != NULL);
bool first = true;
for (int i = 0; i < e->TestsAll.Size; i++)
{
ImGuiTest* failing_test = e->TestsAll[i];
Str* filter = (group == ImGuiTestGroup_Tests) ? e->UiFilterTests : e->UiFilterPerfs;
if (failing_test->Group != group)
continue;
if (failing_test->Output.Status != ImGuiTestStatus_Error)
continue;
if (!ImGuiTestEngine_PassFilter(failing_test, filter->empty() ? "all" : filter->c_str()))
continue;
if (!first)
out_string->append(separator);
out_string->append(failing_test->Name);
first = false;
}
}
static void TestStatusButton(const char* id, const ImVec4& color, bool running, int display_counter)
{
ImGuiContext& g = *GImGui;
ImGui::PushItemFlag(ImGuiItemFlags_NoTabStop, true);
ImGui::ColorButton(id, color, ImGuiColorEditFlags_NoTooltip);
ImGui::PopItemFlag();
if (running)
{
//ImRect r = g.LastItemData.Rect;
ImVec2 center = g.LastItemData.Rect.GetCenter();
float radius = ImFloor(ImMin(g.LastItemData.Rect.GetWidth(), g.LastItemData.Rect.GetHeight()) * 0.40f);
float t = (float)(ImGui::GetTime() * 20.0f);
ImVec2 off(ImCos(t) * radius, ImSin(t) * radius);
ImGui::GetWindowDrawList()->AddLine(center - off, center + off, ImGui::GetColorU32(ImGuiCol_Text), 1.5f);
//ImGui::RenderText(r.Min + style.FramePadding + ImVec2(0, 0), &"|\0/\0-\0\\"[(((ImGui::GetFrameCount() / 5) & 3) << 1)], NULL);
}
else if (display_counter >= 0)
{
ImVec2 center = g.LastItemData.Rect.GetCenter();
Str30f buf("%d", display_counter);
ImGui::GetWindowDrawList()->AddText(center - ImGui::CalcTextSize(buf.c_str()) * 0.5f, ImGui::GetColorU32(ImGuiCol_Text), buf.c_str());
}
}
static void ShowTestGroup(ImGuiTestEngine* e, ImGuiTestGroup group, Str* filter)
{
ImGuiStyle& style = ImGui::GetStyle();
ImGuiIO& io = ImGui::GetIO();
const float dpi_scale = GetDpiScale();
// Colored Status button: will be displayed later below
// - Save position of test run status button and make space for it.
const ImVec2 status_button_pos = ImGui::GetCursorPos();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetFrameHeight() + style.ItemInnerSpacing.x);
//ImGui::Text("TESTS (%d)", engine->TestsAll.Size);
#if IMGUI_VERSION_NUM >= 18837
bool run = ImGui::Button("Run") || ImGui::Shortcut(ImGuiMod_Ctrl | ImGuiKey_R);
#else
bool = ImGui::Button("Run");
#endif
#if IMGUI_VERSION_NUM > 18963
ImGui::SetItemTooltip("Ctrl+R");
#endif
if (run)
{
for (int n = 0; n < e->TestsAll.Size; n++)
{
ImGuiTest* test = e->TestsAll[n];
if (!ShowTestGroupFilterTest(e, group, filter->c_str(), test))
continue;
ImGuiTestEngine_QueueTest(e, test, ImGuiTestRunFlags_None);
}
}
ImGui::SameLine();
{
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6.0f);
const char* filter_by_status_desc = "";
if (e->UiFilterByStatusMask == ~0u)
filter_by_status_desc = "All";
else if (e->UiFilterByStatusMask == ~(1u << ImGuiTestStatus_Success))
filter_by_status_desc = "Not OK";
else if (e->UiFilterByStatusMask == (1u << ImGuiTestStatus_Error))
filter_by_status_desc = "Errors";
if (ImGui::BeginCombo("##filterbystatus", filter_by_status_desc))
{
if (ImGui::Selectable("All", e->UiFilterByStatusMask == ~0u))
e->UiFilterByStatusMask = (ImU32)~0u;
if (ImGui::Selectable("Not OK", e->UiFilterByStatusMask == ~(1u << ImGuiTestStatus_Success)))
e->UiFilterByStatusMask = (ImU32)~(1u << ImGuiTestStatus_Success);
if (ImGui::Selectable("Errors", e->UiFilterByStatusMask == (1u << ImGuiTestStatus_Error)))
e->UiFilterByStatusMask = (ImU32)(1u << ImGuiTestStatus_Error);
ImGui::EndCombo();
}
}
ImGui::SameLine();
const char* perflog_label = "Perf Tool";
float filter_width = ImGui::GetWindowContentRegionMax().x - ImGui::GetCursorPos().x;
float perf_stress_factor_width = (30 * dpi_scale);
if (group == ImGuiTestGroup_Perfs)
{
filter_width -= style.ItemSpacing.x + perf_stress_factor_width;
filter_width -= style.ItemSpacing.x + style.FramePadding.x * 2 + ImGui::CalcTextSize(perflog_label).x;
}
filter_width -= ImGui::CalcTextSize("(?)").x + style.ItemSpacing.x;
ImGui::SetNextItemWidth(ImMax(20.0f, filter_width));
ImGui::InputText("##filter", filter);
ImGui::SameLine();
ImGui::TextDisabled("(?)");
ImGui::SetItemTooltip("Query is composed of one or more comma-separated filter terms with optional modifiers.\n"
"Available modifiers:\n"
"- '-' prefix excludes tests matched by the term.\n"
"- '^' prefix anchors term matching to the start of the string.\n"
"- '$' suffix anchors term matching to the end of the string.");
if (group == ImGuiTestGroup_Perfs)
{
ImGui::SameLine();
ImGui::SetNextItemWidth(perf_stress_factor_width);
ImGui::DragInt("##PerfStress", &e->IO.PerfStressAmount, 0.1f, 1, 20, "x%d");
ImGui::SetItemTooltip("Increase workload of performance tests (higher means longer run)."); // FIXME: Move?
ImGui::SameLine();
if (ImGui::Button(perflog_label))
{
e->UiPerfToolOpen = true;
ImGui::FocusWindow(ImGui::FindWindowByName("Dear ImGui Perf Tool"));
}
}
int tests_completed = 0;
int tests_succeeded = 0;
int tests_failed = 0;
if (ImGui::BeginTable("Tests", 3, ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable | ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_SizingFixedFit))
{
ImGui::TableSetupScrollFreeze(0, 1);
ImGui::TableSetupColumn("Status");
ImGui::TableSetupColumn("Category");
ImGui::TableSetupColumn("Test", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableHeadersRow();
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6, 4) * dpi_scale);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4, 0) * dpi_scale);
//ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(100, 10) * dpi_scale);
for (int test_n = 0; test_n < e->TestsAll.Size; test_n++)
{
ImGuiTest* test = e->TestsAll[test_n];
if (!ShowTestGroupFilterTest(e, group, filter->c_str(), test))
continue;
ImGuiTestOutput* test_output = &test->Output;
ImGuiTestContext* test_context = (e->TestContext && e->TestContext->Test == test) ? e->TestContext : NULL; // Running context, if any
ImGui::TableNextRow();
ImGui::PushID(test_n);
// Colors match general test status colors defined below.
ImVec4 status_color;
switch (test_output->Status)
{
case ImGuiTestStatus_Error:
status_color = ImVec4(0.9f, 0.1f, 0.1f, 1.0f);
tests_completed++;
tests_failed++;
break;
case ImGuiTestStatus_Success:
status_color = ImVec4(0.1f, 0.9f, 0.1f, 1.0f);
tests_completed++;
tests_succeeded++;
break;
case ImGuiTestStatus_Queued:
case ImGuiTestStatus_Running:
case ImGuiTestStatus_Suspended:
if (test_context && (test_context->RunFlags & ImGuiTestRunFlags_GuiFuncOnly))
status_color = ImVec4(0.8f, 0.0f, 0.8f, 1.0f);
else
status_color = ImVec4(0.8f, 0.4f, 0.1f, 1.0f);
break;
default:
status_color = ImVec4(0.4f, 0.4f, 0.4f, 1.0f);
break;
}
ImGui::TableNextColumn();
TestStatusButton("status", status_color, test_output->Status == ImGuiTestStatus_Running || test_output->Status == ImGuiTestStatus_Suspended, -1);
ImGui::SameLine();
bool queue_test = false;
bool queue_gui_func_toggle = false;
bool select_test = false;
if (test_output->Status == ImGuiTestStatus_Suspended)
{
// Resume IM_SUSPEND_TESTFUNC
// FIXME: Terrible user experience to have this here.
if (ImGui::Button("Con###Run"))
test_output->Status = ImGuiTestStatus_Running;
ImGui::SetItemTooltip("CTRL+Space to continue.");
if (ImGui::IsKeyPressed(ImGuiKey_Space) && io.KeyCtrl)
test_output->Status = ImGuiTestStatus_Running;
}
else
{
if (ImGui::Button("Run###Run"))
queue_test = select_test = true;
}
ImGui::TableNextColumn();
if (ImGui::Selectable(test->Category, test == e->UiSelectedTest, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_SelectOnNav))
select_test = true;
// Double-click to run test, CTRL+Double-click to run GUI function
const bool is_running_gui_func = (test_context && (test_context->RunFlags & ImGuiTestRunFlags_GuiFuncOnly));
const bool has_gui_func = (test->GuiFunc != NULL);
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0))
{
if (ImGui::GetIO().KeyCtrl)
queue_gui_func_toggle = true;
else
queue_test = true;
}
/*if (ImGui::IsItemHovered() && test->TestLog.size() > 0)
{
ImGui::BeginTooltip();
DrawTestLog(engine, test, false);
ImGui::EndTooltip();
}*/
if (e->UiSelectAndScrollToTest == test)
ImGui::SetScrollHereY();
bool view_source = false;
if (ImGui::BeginPopupContextItem())
{
select_test = true;
if (ImGui::MenuItem("Run test"))
queue_test = true;
if (ImGui::MenuItem("Run GUI func", "Ctrl+DblClick", is_running_gui_func, has_gui_func))
queue_gui_func_toggle = true;
ImGui::Separator();
const bool open_source_available = (test->SourceFile != NULL) && (e->IO.SrcFileOpenFunc != NULL);
Str128 buf;
if (test->SourceFile != NULL) // This is normally set by IM_REGISTER_TEST() but custom registration may omit it.
buf.setf("Open source (%s:%d)", ImPathFindFilename(test->SourceFile), test->SourceLine);
else
buf.set("Open source");
if (ImGui::MenuItem(buf.c_str(), NULL, false, open_source_available))
e->IO.SrcFileOpenFunc(test->SourceFile, test->SourceLine, e->IO.SrcFileOpenUserData);
if (ImGui::MenuItem("View source...", NULL, false, test->SourceFile != NULL))
view_source = true;
if (group == ImGuiTestGroup_Perfs && ImGui::MenuItem("View perflog"))
{
e->PerfTool->ViewOnly(test->Name);
e->UiPerfToolOpen = true;
}
ImGui::Separator();
if (ImGui::MenuItem("Copy name", NULL, false))
ImGui::SetClipboardText(test->Name);
if (test_output->Status == ImGuiTestStatus_Error)
if (ImGui::MenuItem("Copy names of all failing tests"))
{
Str256 failing_tests;
GetFailingTestsAsString(e, group, ',', &failing_tests);
ImGui::SetClipboardText(failing_tests.c_str());
}
ImGuiTestLog* test_log = &test_output->Log;
if (ImGui::BeginMenu("Copy log", !test_log->IsEmpty()))
{
for (int level_n = ImGuiTestVerboseLevel_Error; level_n < ImGuiTestVerboseLevel_COUNT; level_n++)
{
ImGuiTestVerboseLevel level = (ImGuiTestVerboseLevel)level_n;
int count = test_log->ExtractLinesForVerboseLevels((ImGuiTestVerboseLevel)0, level, NULL);
if (ImGui::MenuItem(Str64f("%s (%d lines)", ImGuiTestEngine_GetVerboseLevelName(level), count).c_str(), NULL, false, count > 0))
{
ImGuiTextBuffer buffer;
test_log->ExtractLinesForVerboseLevels((ImGuiTestVerboseLevel)0, level, &buffer);
ImGui::SetClipboardText(buffer.c_str());
}
}
ImGui::EndMenu();
}
if (ImGui::MenuItem("Clear log", NULL, false, !test_log->IsEmpty()))
test_log->Clear();
ImGui::EndPopup();
}
// Process source popup
static ImGuiTextBuffer source_blurb;
static int goto_line = -1;
if (view_source)
{
source_blurb.clear();
size_t file_size = 0;
char* file_data = (char*)ImFileLoadToMemory(test->SourceFile, "rb", &file_size);
if (file_data)
source_blurb.append(file_data, file_data + file_size);
else
source_blurb.append("<Error loading sources>");
goto_line = (test->SourceLine + test->SourceLineEnd) / 2;
ImGui::OpenPopup("Source");
}
if (ImGui::BeginPopup("Source"))
{
// FIXME: Local vs screen pos too messy :(
const ImVec2 start_pos = ImGui::GetCursorStartPos();
const float line_height = ImGui::GetTextLineHeight();
if (goto_line != -1)
ImGui::SetScrollFromPosY(start_pos.y + (goto_line - 1) * line_height, 0.5f);
goto_line = -1;
ImRect r(0.0f, test->SourceLine * line_height, ImGui::GetWindowWidth(), (test->SourceLine + 1) * line_height); // SourceLineEnd is too flaky
ImGui::GetWindowDrawList()->AddRectFilled(ImGui::GetWindowPos() + start_pos + r.Min, ImGui::GetWindowPos() + start_pos + r.Max, IM_COL32(80, 80, 150, 150));
ImGui::TextUnformatted(source_blurb.c_str(), source_blurb.end());
ImGui::EndPopup();
}
ImGui::TableNextColumn();
ImGui::TextUnformatted(test->Name);
// Process selection
if (select_test)
e->UiSelectedTest = test;
// Process queuing
if (queue_gui_func_toggle && is_running_gui_func)
ImGuiTestEngine_AbortCurrentTest(e);
else if (queue_gui_func_toggle && !e->IO.IsRunningTests)
ImGuiTestEngine_QueueTest(e, test, ImGuiTestRunFlags_RunFromGui | ImGuiTestRunFlags_GuiFuncOnly);
if (queue_test && !e->IO.IsRunningTests)
ImGuiTestEngine_QueueTest(e, test, ImGuiTestRunFlags_RunFromGui);
ImGui::PopID();
}
ImGui::Spacing();
ImGui::PopStyleVar(2);
ImGui::EndTable();
}
// Display test status recap (colors match per-test run button colors defined above)
{
ImVec4 status_color;
if (tests_failed > 0)
status_color = ImVec4(0.9f, 0.1f, 0.1f, 1.0f); // Red
else if (e->IO.IsRunningTests)
status_color = ImVec4(0.8f, 0.4f, 0.1f, 1.0f);
else if (tests_succeeded > 0 && tests_completed == tests_succeeded)
status_color = ImVec4(0.1f, 0.9f, 0.1f, 1.0f);
else
status_color = ImVec4(0.4f, 0.4f, 0.4f, 1.0f);
//ImVec2 cursor_pos_bkp = ImGui::GetCursorPos();
ImGui::SetCursorPos(status_button_pos);
TestStatusButton("status", status_color, false, tests_failed > 0 ? tests_failed : -1);// e->IO.IsRunningTests);
ImGui::SetItemTooltip("Filtered: %d\n- OK: %d\n- Errors: %d", tests_completed, tests_succeeded, tests_failed);
//ImGui::SetCursorPos(cursor_pos_bkp); // Restore cursor position for rendering further widgets
}
}
static void ImGuiTestEngine_ShowLogAndTools(ImGuiTestEngine* engine)
{
ImGuiContext& g = *GImGui;
const float dpi_scale = GetDpiScale();
if (!ImGui::BeginTabBar("##tools"))
return;
if (ImGui::BeginTabItem("LOG"))
{
ImGuiTest* selected_test = engine->UiSelectedTest;
if (selected_test != NULL)
ImGui::Text("Log for '%s' '%s'", selected_test->Category, selected_test->Name);
else
ImGui::Text("N/A");
if (ImGui::SmallButton("Clear"))
if (selected_test)
selected_test->Output.Log.Clear();
ImGui::SameLine();
if (ImGui::SmallButton("Copy to clipboard"))
if (engine->UiSelectedTest)
ImGui::SetClipboardText(selected_test->Output.Log.Buffer.c_str());
ImGui::Separator();
ImGui::BeginChild("Log");
if (engine->UiSelectedTest)
{
DrawTestLog(engine, engine->UiSelectedTest);
if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY())
ImGui::SetScrollHereY();
}
ImGui::EndChild();
ImGui::EndTabItem();
}
// Options
if (ImGui::BeginTabItem("OPTIONS"))
{
ImGuiIO& io = ImGui::GetIO();
ImGui::Text("%.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
ImGui::Text("TestEngine: HookItems: %d, HookPushId: %d, InfoTasks: %d", g.TestEngineHookItems, g.DebugHookIdInfo != 0, engine->InfoTasks.Size);
ImGui::Separator();
if (ImGui::Button("Reboot UI context"))
engine->ToolDebugRebootUiContext = true;
const ImGuiInputTextCallback filter_callback = [](ImGuiInputTextCallbackData* data) { return (data->EventChar == ',' || data->EventChar == ';') ? 1 : 0; };
ImGui::InputText("Branch/Annotation", engine->IO.GitBranchName, IM_ARRAYSIZE(engine->IO.GitBranchName), ImGuiInputTextFlags_CallbackCharFilter, filter_callback, NULL);
ImGui::SetItemTooltip("This will be stored in the CSV file for performance tools.");
ImGui::Separator();
if (ImGui::TreeNode("Screen/video capture"))
{
ImGui::Checkbox("Capture when requested by API", &engine->IO.ConfigCaptureEnabled);
ImGui::SetItemTooltip("Enable or disable screen capture API completely.");
ImGui::Checkbox("Capture screen on error", &engine->IO.ConfigCaptureOnError);
ImGui::SetItemTooltip("Capture a screenshot on test failure.");
// Fields modified by in this call will be synced to engine->CaptureContext.
engine->CaptureTool._ShowEncoderConfigFields(&engine->CaptureContext);
ImGui::TreePop();
}
if (ImGui::TreeNode("Performances"))
{
ImGui::Checkbox("Slow down whole app", &engine->ToolSlowDown);
ImGui::SameLine(); ImGui::SetNextItemWidth(70 * dpi_scale);
ImGui::SliderInt("##ms", &engine->ToolSlowDownMs, 0, 400, "%d ms");
// FIXME-TESTS: Need to be visualizing the samples/spikes.
double dt_1 = 1.0 / ImGui::GetIO().Framerate;
double fps_now = 1.0 / dt_1;
double dt_100 = engine->PerfDeltaTime100.GetAverage();
double dt_500 = engine->PerfDeltaTime500.GetAverage();
//if (engine->PerfRefDeltaTime <= 0.0 && engine->PerfRefDeltaTime.IsFull())
// engine->PerfRefDeltaTime = dt_2000;
ImGui::Checkbox("Unthrolled", &engine->IO.ConfigNoThrottle);
ImGui::SameLine();
if (ImGui::Button("Pick ref dt"))
engine->PerfRefDeltaTime = dt_500;
double dt_ref = engine->PerfRefDeltaTime;
ImGui::Text("[ref dt] %6.3f ms", engine->PerfRefDeltaTime * 1000);
ImGui::Text("[last 001] %6.3f ms (%.1f FPS) ++ %6.3f ms", dt_1 * 1000.0, 1.0 / dt_1, (dt_1 - dt_ref) * 1000);
ImGui::Text("[last 100] %6.3f ms (%.1f FPS) ++ %6.3f ms ~ converging in %.1f secs", dt_100 * 1000.0, 1.0 / dt_100, (dt_1 - dt_ref) * 1000, 100.0 / fps_now);
ImGui::Text("[last 500] %6.3f ms (%.1f FPS) ++ %6.3f ms ~ converging in %.1f secs", dt_500 * 1000.0, 1.0 / dt_500, (dt_1 - dt_ref) * 1000, 500.0 / fps_now);
//ImGui::PlotLines("Last 100", &engine->PerfDeltaTime100.Samples.Data, engine->PerfDeltaTime100.Samples.Size, engine->PerfDeltaTime100.Idx, NULL, 0.0f, dt_1000 * 1.10f, ImVec2(0.0f, ImGui::GetFontSize()));
ImVec2 plot_size(0.0f, ImGui::GetFrameHeight() * 3);
ImMovingAverage<double>* ma = &engine->PerfDeltaTime500;
ImGui::PlotLines("Last 500",
[](void* data, int n) { ImMovingAverage<double>* ma = (ImMovingAverage<double>*)data; return (float)(ma->Samples[n] * 1000); },
ma, ma->Samples.Size, 0 * ma->Idx, NULL, 0.0f, (float)(ImMax(dt_100, dt_500) * 1000.0 * 1.2f), plot_size);
ImGui::TreePop();
}
if (ImGui::TreeNode("Dear ImGui Configuration Flags"))
{
ImGui::CheckboxFlags("io.ConfigFlags: NavEnableKeyboard", &io.ConfigFlags, ImGuiConfigFlags_NavEnableKeyboard);
ImGui::CheckboxFlags("io.ConfigFlags: NavEnableGamepad", &io.ConfigFlags, ImGuiConfigFlags_NavEnableGamepad);
#ifdef IMGUI_HAS_DOCK
ImGui::Checkbox("io.ConfigDockingAlwaysTabBar", &io.ConfigDockingAlwaysTabBar);
#endif
ImGui::TreePop();
}
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
static void ImGuiTestEngine_ShowTestTool(ImGuiTestEngine* engine, bool* p_open)
{
const float dpi_scale = GetDpiScale();
ImGui::SetNextWindowSize(ImVec2(ImGui::GetFontSize() * 50, ImGui::GetFontSize() * 40), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("Dear ImGui Test Engine", p_open, ImGuiWindowFlags_MenuBar))
{
ImGui::End();
return;
}
if (ImGui::BeginMenuBar())
{
if (ImGui::BeginMenu("Tools"))
{
ImGuiContext& g = *GImGui;
ImGui::MenuItem("Metrics/Debugger", "", &engine->UiMetricsOpen);
ImGui::MenuItem("Debug Log", "", &engine->UiDebugLogOpen);
ImGui::MenuItem("Stack Tool", "", &engine->UiStackToolOpen);
ImGui::MenuItem("Item Picker", "", &g.DebugItemPickerActive);
ImGui::Separator();
ImGui::MenuItem("Capture Tool", "", &engine->UiCaptureToolOpen);
ImGui::MenuItem("Perf Tool", "", &engine->UiPerfToolOpen);
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
ImGui::SetNextItemWidth(90 * dpi_scale);
if (ImGui::BeginCombo("##RunSpeed", ImGuiTestEngine_GetRunSpeedName(engine->IO.ConfigRunSpeed), ImGuiComboFlags_None))
{
for (ImGuiTestRunSpeed level = (ImGuiTestRunSpeed)0; level < ImGuiTestRunSpeed_COUNT; level = (ImGuiTestRunSpeed)(level + 1))
if (ImGui::Selectable(ImGuiTestEngine_GetRunSpeedName(level), engine->IO.ConfigRunSpeed == level))
engine->IO.ConfigRunSpeed = level;
ImGui::EndCombo();
}
ImGui::SetItemTooltip(
"Running speed\n"
"- Fast: Run tests as fast as possible (no delay/vsync, teleport mouse, etc.).\n"
"- Normal: Run tests at human watchable speed (for debugging).\n"
"- Cinematic: Run tests with pauses between actions (for e.g. tutorials)."
);
ImGui::SameLine();
//ImGui::Checkbox("Fast", &engine->IO.ConfigRunFast);
//ImGui::SameLine();
ImGui::Checkbox("Stop", &engine->IO.ConfigStopOnError);
ImGui::SetItemTooltip("Stop running tests when hitting an error.");
ImGui::SameLine();
ImGui::Checkbox("DbgBrk", &engine->IO.ConfigBreakOnError);
ImGui::SetItemTooltip("Break in debugger when hitting an error.");
ImGui::SameLine();
ImGui::Checkbox("KeepGUI", &engine->IO.ConfigKeepGuiFunc);
ImGui::SetItemTooltip("Keep GUI function running after a test fails, or when a single queued test is finished.\nHold ESC to abort a running GUI function.");
ImGui::SameLine();
ImGui::Checkbox("Refocus", &engine->IO.ConfigRestoreFocusAfterTests);
ImGui::SetItemTooltip("Restore focus back after running tests.");
ImGui::SameLine();
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
ImGui::SameLine();
ImGui::SetNextItemWidth(70 * dpi_scale);
if (ImGui::BeginCombo("##Verbose", ImGuiTestEngine_GetVerboseLevelName(engine->IO.ConfigVerboseLevel), ImGuiComboFlags_None))
{
for (ImGuiTestVerboseLevel level = (ImGuiTestVerboseLevel)0; level < ImGuiTestVerboseLevel_COUNT; level = (ImGuiTestVerboseLevel)(level + 1))
if (ImGui::Selectable(ImGuiTestEngine_GetVerboseLevelName(level), engine->IO.ConfigVerboseLevel == level))
engine->IO.ConfigVerboseLevel = engine->IO.ConfigVerboseLevelOnError = level;
ImGui::EndCombo();
}
ImGui::SetItemTooltip("Verbose level.");
//ImGui::PopStyleVar();
ImGui::Separator();
// SPLITTER
// FIXME-OPT: A better splitter API supporting arbitrary number of splits would be useful.
float list_height = 0.0f;
float& log_height = engine->UiLogHeight;
ImGui::Splitter("splitter", &list_height, &log_height, ImGuiAxis_Y, +1);
// TESTS
ImGui::BeginChild("List", ImVec2(0, list_height), false, ImGuiWindowFlags_NoScrollbar);
if (ImGui::BeginTabBar("##Tests", ImGuiTabBarFlags_NoTooltip)) // Add _NoPushId flag in TabBar?
{
if (ImGui::BeginTabItem("TESTS", NULL, ImGuiTabItemFlags_NoPushId))
{
ShowTestGroup(engine, ImGuiTestGroup_Tests, engine->UiFilterTests);
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("PERFS", NULL, ImGuiTabItemFlags_NoPushId))
{
ShowTestGroup(engine, ImGuiTestGroup_Perfs, engine->UiFilterPerfs);
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
ImGui::EndChild();
engine->UiSelectAndScrollToTest = NULL;
// LOG & TOOLS
ImGui::BeginChild("Log", ImVec2(0, log_height));
ImGuiTestEngine_ShowLogAndTools(engine);
ImGui::EndChild();
ImGui::End();
}
void ImGuiTestEngine_ShowTestEngineWindows(ImGuiTestEngine* e, bool* p_open)
{
// Test Tool
ImGuiTestEngine_ShowTestTool(e, p_open);
// Stack Tool
#if IMGUI_VERSION_NUM < 18993
if (e->UiStackToolOpen)
ImGui::ShowStackToolWindow(&e->UiStackToolOpen);
#else
if (e->UiStackToolOpen)
ImGui::ShowIDStackToolWindow(&e->UiStackToolOpen);
#endif
// Capture Tool
if (e->UiCaptureToolOpen)
e->CaptureTool.ShowCaptureToolWindow(&e->CaptureContext, &e->UiCaptureToolOpen);
// Performance tool
if (e->UiPerfToolOpen)
e->PerfTool->ShowPerfToolWindow(e, &e->UiPerfToolOpen);;
// Show Dear ImGui windows
// (we cannot show demo window here because it could lead to duplicate display, which demo windows isn't guarded for)
if (e->UiMetricsOpen)
ImGui::ShowMetricsWindow(&e->UiMetricsOpen);
if (e->UiDebugLogOpen)
ImGui::ShowDebugLogWindow(&e->UiDebugLogOpen);
}

View File

@@ -0,0 +1,21 @@
// dear imgui test engine
// (ui)
// If you run tests in an interactive or visible application, you may want to call ImGuiTestEngine_ShowTestEngineWindows()
// Provide access to:
// - "Dear ImGui Test Engine" main interface
// - "Dear ImGui Capture Tool"
// - "Dear ImGui Perf Tool"
// - other core debug functions: Metrics, Debug Log
#pragma once
#ifndef IMGUI_VERSION
#include "imgui.h" // IMGUI_API
#endif
// Forward declarations
struct ImGuiTestEngine;
// Functions
IMGUI_API void ImGuiTestEngine_ShowTestEngineWindows(ImGuiTestEngine* engine, bool* p_open);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,221 @@
// dear imgui test engine
// (helpers/utilities. do NOT use this as a general purpose library)
#pragma once
//-----------------------------------------------------------------------------
// Includes
//-----------------------------------------------------------------------------
#include <math.h> // fabsf
#include <stdint.h> // uint64_t
#include <stdio.h> // FILE*
#include "imgui.h" // ImGuiID, ImGuiKey
class Str; // Str<> from thirdparty/Str/Str.h
//-----------------------------------------------------------------------------
// Function Pointers
//-----------------------------------------------------------------------------
#if IMGUI_TEST_ENGINE_ENABLE_STD_FUNCTION
#include <functional>
#define ImFuncPtr(FUNC_TYPE) std::function<FUNC_TYPE>
#else
#define ImFuncPtr(FUNC_TYPE) FUNC_TYPE*
#endif
//-----------------------------------------------------------------------------
// Hashing Helpers
//-----------------------------------------------------------------------------
ImGuiID ImHashDecoratedPath(const char* str, const char* str_end = NULL, ImGuiID seed = 0);
const char* ImFindNextDecoratedPartInPath(const char* str, const char* str_end = NULL);
//-----------------------------------------------------------------------------
// File/Directory Helpers
//-----------------------------------------------------------------------------
bool ImFileExist(const char* filename);
bool ImFileDelete(const char* filename);
bool ImFileCreateDirectoryChain(const char* path, const char* path_end = NULL);
bool ImFileFindInParents(const char* sub_path, int max_parent_count, Str* output);
bool ImFileLoadSourceBlurb(const char* filename, int line_no_start, int line_no_end, ImGuiTextBuffer* out_buf);
//-----------------------------------------------------------------------------
// Path Helpers
//-----------------------------------------------------------------------------
// Those are strictly string manipulation functions
const char* ImPathFindFilename(const char* path, const char* path_end = NULL); // Return value always between path and path_end
const char* ImPathFindExtension(const char* path, const char* path_end = NULL); // Return value always between path and path_end
void ImPathFixSeparatorsForCurrentOS(char* buf);
//-----------------------------------------------------------------------------
// String Helpers
//-----------------------------------------------------------------------------
void ImStrReplace(Str* s, const char* find, const char* repl);
const char* ImStrchrRangeWithEscaping(const char* str, const char* str_end, char find_c);
void ImStrXmlEscape(Str* s);
int ImStrBase64Encode(const unsigned char* src, char* dst, int length);
//-----------------------------------------------------------------------------
// Parsing Helpers
//-----------------------------------------------------------------------------
void ImParseExtractArgcArgvFromCommandLine(int* out_argc, char const*** out_argv, const char* cmd_line);
bool ImParseFindIniSection(const char* ini_config, const char* header, ImVector<char>* result);
//-----------------------------------------------------------------------------
// Time Helpers
//-----------------------------------------------------------------------------
uint64_t ImTimeGetInMicroseconds();
void ImTimestampToISO8601(uint64_t timestamp, Str* out_date);
//-----------------------------------------------------------------------------
// Threading Helpers
//-----------------------------------------------------------------------------
void ImThreadSleepInMilliseconds(int ms);
void ImThreadSetCurrentThreadDescription(const char* description);
//-----------------------------------------------------------------------------
// Build Info helpers
//-----------------------------------------------------------------------------
// All the pointers are expect to be literals/persistent
struct ImBuildInfo
{
const char* Type = "";
const char* Cpu = "";
const char* OS = "";
const char* Compiler = "";
char Date[32]; // "YYYY-MM-DD"
const char* Time = "";
};
const ImBuildInfo* ImBuildGetCompilationInfo();
bool ImBuildFindGitBranchName(const char* git_repo_path, Str* branch_name);
//-----------------------------------------------------------------------------
// Operating System Helpers
//-----------------------------------------------------------------------------
enum ImOsConsoleStream
{
ImOsConsoleStream_StandardOutput,
ImOsConsoleStream_StandardError,
};
enum ImOsConsoleTextColor
{
ImOsConsoleTextColor_Black,
ImOsConsoleTextColor_White,
ImOsConsoleTextColor_BrightWhite,
ImOsConsoleTextColor_BrightRed,
ImOsConsoleTextColor_BrightGreen,
ImOsConsoleTextColor_BrightBlue,
ImOsConsoleTextColor_BrightYellow,
};
bool ImOsCreateProcess(const char* cmd_line);
FILE* ImOsPOpen(const char* cmd_line, const char* mode);
void ImOsPClose(FILE* fp);
void ImOsOpenInShell(const char* path);
bool ImOsIsDebuggerPresent();
void ImOsOutputDebugString(const char* message);
void ImOsConsoleSetTextColor(ImOsConsoleStream stream, ImOsConsoleTextColor color);
//-----------------------------------------------------------------------------
// Miscellaneous functions
//-----------------------------------------------------------------------------
// Tables functions
struct ImGuiTable;
ImGuiID TableGetHeaderID(ImGuiTable* table, const char* column, int instance_no = 0);
ImGuiID TableGetHeaderID(ImGuiTable* table, int column_n, int instance_no = 0);
void TableDiscardInstanceAndSettings(ImGuiID table_id);
// DrawData functions
void DrawDataVerifyMatchingBufferCount(ImDrawData* draw_data);
//-----------------------------------------------------------------------------
// Helper: maintain/calculate moving average
//-----------------------------------------------------------------------------
template<typename TYPE>
struct ImMovingAverage
{
// Internal Fields
ImVector<TYPE> Samples;
TYPE Accum;
int Idx;
int FillAmount;
// Functions
ImMovingAverage() { Accum = (TYPE)0; Idx = FillAmount = 0; }
void Init(int count) { Samples.resize(count); memset(Samples.Data, 0, (size_t)Samples.Size * sizeof(TYPE)); Accum = (TYPE)0; Idx = FillAmount = 0; }
void AddSample(TYPE v) { Accum += v - Samples[Idx]; Samples[Idx] = v; if (++Idx == Samples.Size) Idx = 0; if (FillAmount < Samples.Size) FillAmount++; }
TYPE GetAverage() const { return Accum / (TYPE)FillAmount; }
int GetSampleCount() const { return Samples.Size; }
bool IsFull() const { return FillAmount == Samples.Size; }
};
//-----------------------------------------------------------------------------
// Helper: Simple/dumb CSV parser
//-----------------------------------------------------------------------------
struct ImGuiCsvParser
{
// Public fields
int Columns = 0; // Number of columns in CSV file.
int Rows = 0; // Number of rows in CSV file.
// Internal fields
char* _Data = NULL; // CSV file data.
ImVector<char*> _Index; // CSV table: _Index[row * _Columns + col].
// Functions
ImGuiCsvParser(int columns = -1) { Columns = columns; }
~ImGuiCsvParser() { Clear(); }
bool Load(const char* file_name); // Open and parse a CSV file.
void Clear(); // Free allocated buffers.
const char* GetCell(int row, int col) { IM_ASSERT(0 <= row && row < Rows && 0 <= col && col < Columns); return _Index[row * Columns + col]; }
};
//-----------------------------------------------------------------------------
// Misc Dear ImGui extensions
//-----------------------------------------------------------------------------
#if IMGUI_VERSION_NUM < 18924
struct ImGuiTabBar;
struct ImGuiTabItem;
#endif
namespace ImGui
{
IMGUI_API void ItemErrorFrame(ImU32 col);
#if IMGUI_VERSION_NUM < 18927
ImGuiID TableGetInstanceID(ImGuiTable* table, int instance_no = 0);
#endif
// Str support for InputText()
IMGUI_API bool InputText(const char* label, Str* str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);
IMGUI_API bool InputTextWithHint(const char* label, const char* hint, Str* str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);
IMGUI_API bool InputTextMultiline(const char* label, Str* str, const ImVec2& size = ImVec2(0, 0), ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);
// Splitter
IMGUI_API bool Splitter(const char* id, float* value_1, float* value_2, int axis, int anchor = 0, float min_size_0 = -1.0f, float min_size_1 = -1.0f);
// Misc
IMGUI_API ImFont* FindFontByPrefix(const char* name);
// Legacy version support
#if IMGUI_VERSION_NUM < 18924
IMGUI_API const char* TabBarGetTabName(ImGuiTabBar* tab_bar, ImGuiTabItem* tab);
#endif
}

View File

@@ -0,0 +1,7 @@
## Third party libraries used by Test Engine
Always used:
- `Str/Str.h` simple string type, used by `imgui_test_engine` (Public Domain)
Used if `IMGUI_TEST_ENGINE_ENABLE_CAPTURE` is defined to 1 (default: 1)
- `stb/imstb_image_write.h` image writer, used by `imgui_capture_tool` (MIT Licence OR Public Domain)

View File

@@ -0,0 +1,71 @@
```
Str
Simple C++ string type with an optional local buffer, by Omar Cornut
https://github.com/ocornut/str
LICENSE
This software is in the public domain. Where that dedication is not
recognized, you are granted a perpetual, irrevocable license to copy,
distribute, and modify this file as you see fit.
USAGE
Include Str.h in whatever places need to refer to it.
In ONE .cpp file, write '#define STR_IMPLEMENTATION' before the #include.
This expands out the actual implementation into that C/C++ file.
NOTES
- This isn't a fully featured string class.
- It is a simple, bearable replacement to std::string that isn't heap abusive nor bloated (can actually be debugged by humans!).
- String are mutable. We don't maintain size so length() is not-constant time.
- Maximum string size currently limited to 2 MB (we allocate 21 bits to hold capacity).
- Local buffer size is currently limited to 1023 bytes (we allocate 10 bits to hold local buffer size).
- We could easily raise those limits if we are ok to increase the structure overhead in 32-bits mode.
- In "non-owned" mode for literals/reference we don't do any tracking/counting of references.
- Overhead is 8-bytes in 32-bits, 16-bytes in 64-bits (12 + alignment).
- I'm using this code but it hasn't been tested thoroughly.
The idea is that you can provide an arbitrary sized local buffer if you expect string to fit
most of the time, and then you avoid using costly heap.
No local buffer, always use heap, sizeof()==8~16 (depends if your pointers are 32-bits or 64-bits)
Str s = "hey"; // use heap
With a local buffer of 16 bytes, sizeof() == 8~16 + 16 bytes.
Str16 s = "filename.h"; // copy into local buffer
Str16 s = "long_filename_not_very_long_but_longer_than_expected.h"; // use heap
With a local buffer of 256 bytes, sizeof() == 8~16 + 256 bytes.
Str256 s = "long_filename_not_very_long_but_longer_than_expected.h"; // copy into local buffer
Common sizes are defined at the bottom of Str.h, you may define your own.
Functions:
Str256 s;
s.set("hello sailor"); // set (copy)
s.setf("%s/%s.tmp", folder, filename); // set (w/format)
s.append("hello"); // append. cost a length() calculation!
s.appendf("hello %d", 42); // append (w/format). cost a length() calculation!
s.set_ref("Hey!"); // set (literal/reference, just copy pointer, no tracking)
Constructor helper for format string: add a trailing 'f' to the type. Underlying type is the same.
Str256f filename("%s/%s.tmp", folder, filename); // construct (w/format)
fopen(Str256f("%s/%s.tmp, folder, filename).c_str(), "rb"); // construct (w/format), use as function param, destruct
Constructor helper for reference/literal:
StrRef ref("literal"); // copy pointer, no allocation, no string copy
StrRef ref2(GetDebugName()); // copy pointer. no tracking of anything whatsoever, know what you are doing!
All StrXXX types derives from Str and instance hold the local buffer capacity.
So you can pass e.g. Str256* to a function taking base type Str* and it will be functional!
void MyFunc(Str& s) { s = "Hello"; } // will use local buffer if available in Str instance
(Using a template e.g. Str<N> we could remove the LocalBufSize storage but it would make passing typed Str<> to functions tricky.
Instead we don't use template so you can pass them around as the base type Str*. Also, templates are ugly.)
```

View File

@@ -0,0 +1,658 @@
// Str v0.32
// Simple C++ string type with an optional local buffer, by Omar Cornut
// https://github.com/ocornut/str
// LICENSE
// This software is in the public domain. Where that dedication is not
// recognized, you are granted a perpetual, irrevocable license to copy,
// distribute, and modify this file as you see fit.
// USAGE
// Include this file in whatever places need to refer to it.
// In ONE .cpp file, write '#define STR_IMPLEMENTATION' before the #include of this file.
// This expands out the actual implementation into that C/C++ file.
/*
- This isn't a fully featured string class.
- It is a simple, bearable replacement to std::string that isn't heap abusive nor bloated (can actually be debugged by humans).
- String are mutable. We don't maintain size so length() is not-constant time.
- Maximum string size currently limited to 2 MB (we allocate 21 bits to hold capacity).
- Local buffer size is currently limited to 1023 bytes (we allocate 10 bits to hold local buffer size).
- In "non-owned" mode for literals/reference we don't do any tracking/counting of references.
- Overhead is 8-bytes in 32-bits, 16-bytes in 64-bits (12 + alignment).
- This code hasn't been tested very much. it is probably incomplete or broken. Made it for my own use.
The idea is that you can provide an arbitrary sized local buffer if you expect string to fit
most of the time, and then you avoid using costly heap.
No local buffer, always use heap, sizeof()==8~16 (depends if your pointers are 32-bits or 64-bits)
Str s = "hey";
With a local buffer of 16 bytes, sizeof() == 8~16 + 16 bytes.
Str16 s = "filename.h"; // copy into local buffer
Str16 s = "long_filename_not_very_long_but_longer_than_expected.h"; // use heap
With a local buffer of 256 bytes, sizeof() == 8~16 + 256 bytes.
Str256 s = "long_filename_not_very_long_but_longer_than_expected.h"; // copy into local buffer
Common sizes are defined at the bottom of Str.h, you may define your own.
Functions:
Str256 s;
s.set("hello sailor"); // set (copy)
s.setf("%s/%s.tmp", folder, filename); // set (w/format)
s.append("hello"); // append. cost a length() calculation!
s.appendf("hello %d", 42); // append (w/format). cost a length() calculation!
s.set_ref("Hey!"); // set (literal/reference, just copy pointer, no tracking)
Constructor helper for format string: add a trailing 'f' to the type. Underlying type is the same.
Str256f filename("%s/%s.tmp", folder, filename); // construct (w/format)
fopen(Str256f("%s/%s.tmp, folder, filename).c_str(), "rb"); // construct (w/format), use as function param, destruct
Constructor helper for reference/literal:
StrRef ref("literal"); // copy pointer, no allocation, no string copy
StrRef ref2(GetDebugName()); // copy pointer. no tracking of anything whatsoever, know what you are doing!
All StrXXX types derives from Str and instance hold the local buffer capacity. So you can pass e.g. Str256* to a function taking base type Str* and it will be functional.
void MyFunc(Str& s) { s = "Hello"; } // will use local buffer if available in Str instance
(Using a template e.g. Str<N> we could remove the LocalBufSize storage but it would make passing typed Str<> to functions tricky.
Instead we don't use template so you can pass them around as the base type Str*. Also, templates are ugly.)
*/
/*
CHANGELOG
0.32 - added owned() accessor.
0.31 - fixed various warnings.
0.30 - turned into a single header file, removed Str.cpp.
0.29 - fixed bug when calling reserve on non-owned strings (ie. when using StrRef or set_ref), and fixed <string> include.
0.28 - breaking change: replaced Str32 by Str30 to avoid collision with Str32 from MacTypes.h .
0.27 - added STR_API and basic .natvis file.
0.26 - fixed set(cont char* src, const char* src_end) writing null terminator to the wrong position.
0.25 - allow set(const char* NULL) or operator= NULL to clear the string. note that set() from range or other types are not allowed.
0.24 - allow set_ref(const char* NULL) to clear the string. include fixes for linux.
0.23 - added append(char). added append_from(int idx, XXX) functions. fixed some compilers warnings.
0.22 - documentation improvements, comments. fixes for some compilers.
0.21 - added StrXXXf() constructor to construct directly from a format string.
*/
/*
TODO
- Since we lose 4-bytes of padding on 64-bits architecture, perhaps just spread the header to 8-bytes and lift size limits?
- More functions/helpers.
*/
#ifndef STR_INCLUDED
#define STR_INCLUDED
//-------------------------------------------------------------------------
// CONFIGURATION
//-------------------------------------------------------------------------
#ifndef STR_MEMALLOC
#define STR_MEMALLOC malloc
#include <stdlib.h>
#endif
#ifndef STR_MEMFREE
#define STR_MEMFREE free
#include <stdlib.h>
#endif
#ifndef STR_ASSERT
#define STR_ASSERT assert
#include <assert.h>
#endif
#ifndef STR_API
#define STR_API
#endif
#include <stdarg.h> // for va_list
#include <string.h> // for strlen, strcmp, memcpy, etc.
// Configuration: #define STR_SUPPORT_STD_STRING 0 to disable setters variants using const std::string& (on by default)
#ifndef STR_SUPPORT_STD_STRING
#define STR_SUPPORT_STD_STRING 1
#endif
// Configuration: #define STR_DEFINE_STR32 1 to keep defining Str32/Str32f, but be warned: on macOS/iOS, MacTypes.h also defines a type named Str32.
#ifndef STR_DEFINE_STR32
#define STR_DEFINE_STR32 0
#endif
#if STR_SUPPORT_STD_STRING
#include <string>
#endif
//-------------------------------------------------------------------------
// HEADERS
//-------------------------------------------------------------------------
// This is the base class that you can pass around
// Footprint is 8-bytes (32-bits arch) or 16-bytes (64-bits arch)
class STR_API Str
{
char* Data; // Point to LocalBuf() or heap allocated
int Capacity : 21; // Max 2 MB
int LocalBufSize : 10; // Max 1023 bytes
unsigned int Owned : 1; // Set when we have ownership of the pointed data (most common, unless using set_ref() method or StrRef constructor)
public:
inline char* c_str() { return Data; }
inline const char* c_str() const { return Data; }
inline bool empty() const { return Data[0] == 0; }
inline int length() const { return (int)strlen(Data); } // by design, allow user to write into the buffer at any time
inline int capacity() const { return Capacity; }
inline bool owned() const { return Owned ? true : false; }
inline void set_ref(const char* src);
int setf(const char* fmt, ...);
int setfv(const char* fmt, va_list args);
int setf_nogrow(const char* fmt, ...);
int setfv_nogrow(const char* fmt, va_list args);
int append(char c);
int append(const char* s, const char* s_end = NULL);
int appendf(const char* fmt, ...);
int appendfv(const char* fmt, va_list args);
int append_from(int idx, char c);
int append_from(int idx, const char* s, const char* s_end = NULL); // If you know the string length or want to append from a certain point
int appendf_from(int idx, const char* fmt, ...);
int appendfv_from(int idx, const char* fmt, va_list args);
void clear();
void reserve(int cap);
void reserve_discard(int cap);
void shrink_to_fit();
inline char& operator[](size_t i) { return Data[i]; }
inline char operator[](size_t i) const { return Data[i]; }
//explicit operator const char*() const{ return Data; }
inline Str();
inline Str(const char* rhs);
inline void set(const char* src);
inline void set(const char* src, const char* src_end);
inline Str& operator=(const char* rhs) { set(rhs); return *this; }
inline bool operator==(const char* rhs) const { return strcmp(c_str(), rhs) == 0; }
inline Str(const Str& rhs);
inline void set(const Str& src);
inline Str& operator=(const Str& rhs) { set(rhs); return *this; }
inline bool operator==(const Str& rhs) const { return strcmp(c_str(), rhs.c_str()) == 0; }
#if STR_SUPPORT_STD_STRING
inline Str(const std::string& rhs);
inline void set(const std::string& src);
inline Str& operator=(const std::string& rhs) { set(rhs); return *this; }
inline bool operator==(const std::string& rhs)const { return strcmp(c_str(), rhs.c_str()) == 0; }
#endif
// Destructor for all variants
inline ~Str()
{
if (Owned && !is_using_local_buf())
STR_MEMFREE(Data);
}
static char* EmptyBuffer;
protected:
inline char* local_buf() { return (char*)this + sizeof(Str); }
inline const char* local_buf() const { return (char*)this + sizeof(Str); }
inline bool is_using_local_buf() const { return Data == local_buf() && LocalBufSize != 0; }
// Constructor for StrXXX variants with local buffer
Str(unsigned short local_buf_size)
{
STR_ASSERT(local_buf_size < 1024);
Data = local_buf();
Data[0] = '\0';
Capacity = local_buf_size;
LocalBufSize = local_buf_size;
Owned = 1;
}
};
void Str::set(const char* src)
{
// We allow set(NULL) or via = operator to clear the string.
if (src == NULL)
{
clear();
return;
}
int buf_len = (int)strlen(src)+1;
if (Capacity < buf_len)
reserve_discard(buf_len);
memcpy(Data, src, (size_t)buf_len);
Owned = 1;
}
void Str::set(const char* src, const char* src_end)
{
STR_ASSERT(src != NULL && src_end >= src);
int buf_len = (int)(src_end-src)+1;
if ((int)Capacity < buf_len)
reserve_discard(buf_len);
memcpy(Data, src, (size_t)(buf_len - 1));
Data[buf_len-1] = 0;
Owned = 1;
}
void Str::set(const Str& src)
{
int buf_len = (int)strlen(src.c_str())+1;
if ((int)Capacity < buf_len)
reserve_discard(buf_len);
memcpy(Data, src.c_str(), (size_t)buf_len);
Owned = 1;
}
#if STR_SUPPORT_STD_STRING
void Str::set(const std::string& src)
{
int buf_len = (int)src.length()+1;
if ((int)Capacity < buf_len)
reserve_discard(buf_len);
memcpy(Data, src.c_str(), (size_t)buf_len);
Owned = 1;
}
#endif
inline void Str::set_ref(const char* src)
{
if (Owned && !is_using_local_buf())
STR_MEMFREE(Data);
Data = src ? (char*)src : EmptyBuffer;
Capacity = 0;
Owned = 0;
}
Str::Str()
{
Data = EmptyBuffer; // Shared READ-ONLY initial buffer for 0 capacity
Capacity = 0;
LocalBufSize = 0;
Owned = 0;
}
Str::Str(const Str& rhs) : Str()
{
set(rhs);
}
Str::Str(const char* rhs) : Str()
{
set(rhs);
}
#if STR_SUPPORT_STD_STRING
Str::Str(const std::string& rhs) : Str()
{
set(rhs);
}
#endif
// Literal/reference string
class StrRef : public Str
{
public:
StrRef(const char* s) : Str() { set_ref(s); }
};
// Types embedding a local buffer
// NB: we need to override the constructor and = operator for both Str& and TYPENAME (without the later compiler will call a default copy operator)
#if STR_SUPPORT_STD_STRING
#define STR_DEFINETYPE(TYPENAME, LOCALBUFSIZE) \
class TYPENAME : public Str \
{ \
char local_buf[LOCALBUFSIZE]; \
public: \
TYPENAME() : Str(LOCALBUFSIZE) {} \
TYPENAME(const Str& rhs) : Str(LOCALBUFSIZE) { set(rhs); } \
TYPENAME(const char* rhs) : Str(LOCALBUFSIZE) { set(rhs); } \
TYPENAME(const TYPENAME& rhs) : Str(LOCALBUFSIZE) { set(rhs); } \
TYPENAME(const std::string& rhs) : Str(LOCALBUFSIZE) { set(rhs); } \
TYPENAME& operator=(const char* rhs) { set(rhs); return *this; } \
TYPENAME& operator=(const Str& rhs) { set(rhs); return *this; } \
TYPENAME& operator=(const TYPENAME& rhs) { set(rhs); return *this; } \
TYPENAME& operator=(const std::string& rhs) { set(rhs); return *this; } \
};
#else
#define STR_DEFINETYPE(TYPENAME, LOCALBUFSIZE) \
class TYPENAME : public Str \
{ \
char local_buf[LOCALBUFSIZE]; \
public: \
TYPENAME() : Str(LOCALBUFSIZE) {} \
TYPENAME(const Str& rhs) : Str(LOCALBUFSIZE) { set(rhs); } \
TYPENAME(const char* rhs) : Str(LOCALBUFSIZE) { set(rhs); } \
TYPENAME(const TYPENAME& rhs) : Str(LOCALBUFSIZE) { set(rhs); } \
TYPENAME& operator=(const char* rhs) { set(rhs); return *this; } \
TYPENAME& operator=(const Str& rhs) { set(rhs); return *this; } \
TYPENAME& operator=(const TYPENAME& rhs) { set(rhs); return *this; } \
};
#endif
// Disable PVS-Studio warning V730: Not all members of a class are initialized inside the constructor (local_buf is not initialized and that is fine)
// -V:STR_DEFINETYPE:730
// Helper to define StrXXXf constructors
#define STR_DEFINETYPE_F(TYPENAME, TYPENAME_F) \
class TYPENAME_F : public TYPENAME \
{ \
public: \
TYPENAME_F(const char* fmt, ...) : TYPENAME() { va_list args; va_start(args, fmt); setfv(fmt, args); va_end(args); } \
};
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-private-field" // warning : private field 'local_buf' is not used
#endif
// Declaring types for common sizes here
STR_DEFINETYPE(Str16, 16)
STR_DEFINETYPE(Str30, 30)
STR_DEFINETYPE(Str64, 64)
STR_DEFINETYPE(Str128, 128)
STR_DEFINETYPE(Str256, 256)
STR_DEFINETYPE(Str512, 512)
// Declaring helper constructors to pass in format strings in one statement
STR_DEFINETYPE_F(Str16, Str16f)
STR_DEFINETYPE_F(Str30, Str30f)
STR_DEFINETYPE_F(Str64, Str64f)
STR_DEFINETYPE_F(Str128, Str128f)
STR_DEFINETYPE_F(Str256, Str256f)
STR_DEFINETYPE_F(Str512, Str512f)
#if STR_DEFINE_STR32
STR_DEFINETYPE(Str32, 32)
STR_DEFINETYPE_F(Str32, Str32f)
#endif
#ifdef __clang__
#pragma clang diagnostic pop
#endif
#endif // #ifndef STR_INCLUDED
//-------------------------------------------------------------------------
// IMPLEMENTATION
//-------------------------------------------------------------------------
#ifdef STR_IMPLEMENTATION
#include <stdio.h> // for vsnprintf
// On some platform vsnprintf() takes va_list by reference and modifies it.
// va_copy is the 'correct' way to copy a va_list but Visual Studio prior to 2013 doesn't have it.
#ifndef va_copy
#define va_copy(dest, src) (dest = src)
#endif
// Static empty buffer we can point to for empty strings
// Pointing to a literal increases the like-hood of getting a crash if someone attempts to write in the empty string buffer.
char* Str::EmptyBuffer = (char*)"\0NULL";
// Clear
void Str::clear()
{
if (Owned && !is_using_local_buf())
STR_MEMFREE(Data);
if (LocalBufSize)
{
Data = local_buf();
Data[0] = '\0';
Capacity = LocalBufSize;
Owned = 1;
}
else
{
Data = EmptyBuffer;
Capacity = 0;
Owned = 0;
}
}
// Reserve memory, preserving the current of the buffer
void Str::reserve(int new_capacity)
{
if (new_capacity <= Capacity)
return;
char* new_data;
if (new_capacity < LocalBufSize)
{
// Disowned -> LocalBuf
new_data = local_buf();
new_capacity = LocalBufSize;
}
else
{
// Disowned or LocalBuf -> Heap
new_data = (char*)STR_MEMALLOC((size_t)new_capacity * sizeof(char));
}
// string in Data might be longer than new_capacity if it wasn't owned, don't copy too much
#ifdef _MSC_VER
strncpy_s(new_data, (size_t)new_capacity, Data, (size_t)new_capacity - 1);
#else
strncpy(new_data, Data, (size_t)new_capacity - 1);
#endif
new_data[new_capacity - 1] = 0;
if (Owned && !is_using_local_buf())
STR_MEMFREE(Data);
Data = new_data;
Capacity = new_capacity;
Owned = 1;
}
// Reserve memory, discarding the current of the buffer (if we expect to be fully rewritten)
void Str::reserve_discard(int new_capacity)
{
if (new_capacity <= Capacity)
return;
if (Owned && !is_using_local_buf())
STR_MEMFREE(Data);
if (new_capacity < LocalBufSize)
{
// Disowned -> LocalBuf
Data = local_buf();
Capacity = LocalBufSize;
}
else
{
// Disowned or LocalBuf -> Heap
Data = (char*)STR_MEMALLOC((size_t)new_capacity * sizeof(char));
Capacity = new_capacity;
}
Owned = 1;
}
void Str::shrink_to_fit()
{
if (!Owned || is_using_local_buf())
return;
int new_capacity = length() + 1;
if (Capacity <= new_capacity)
return;
char* new_data = (char*)STR_MEMALLOC((size_t)new_capacity * sizeof(char));
memcpy(new_data, Data, (size_t)new_capacity);
STR_MEMFREE(Data);
Data = new_data;
Capacity = new_capacity;
}
// FIXME: merge setfv() and appendfv()?
int Str::setfv(const char* fmt, va_list args)
{
// Needed for portability on platforms where va_list are passed by reference and modified by functions
va_list args2;
va_copy(args2, args);
// MSVC returns -1 on overflow when writing, which forces us to do two passes
// FIXME-OPT: Find a way around that.
#ifdef _MSC_VER
int len = vsnprintf(NULL, 0, fmt, args);
STR_ASSERT(len >= 0);
if (Capacity < len + 1)
reserve_discard(len + 1);
len = vsnprintf(Data, len + 1, fmt, args2);
#else
// First try
int len = vsnprintf(Owned ? Data : NULL, Owned ? (size_t)Capacity : 0, fmt, args);
STR_ASSERT(len >= 0);
if (Capacity < len + 1)
{
reserve_discard(len + 1);
len = vsnprintf(Data, (size_t)len + 1, fmt, args2);
}
#endif
STR_ASSERT(Owned);
return len;
}
int Str::setf(const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
int len = setfv(fmt, args);
va_end(args);
return len;
}
int Str::setfv_nogrow(const char* fmt, va_list args)
{
STR_ASSERT(Owned);
if (Capacity == 0)
return 0;
int w = vsnprintf(Data, (size_t)Capacity, fmt, args);
Data[Capacity - 1] = 0;
Owned = 1;
return (w == -1) ? Capacity - 1 : w;
}
int Str::setf_nogrow(const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
int len = setfv_nogrow(fmt, args);
va_end(args);
return len;
}
int Str::append_from(int idx, char c)
{
int add_len = 1;
if (Capacity < idx + add_len + 1)
reserve(idx + add_len + 1);
Data[idx] = c;
Data[idx + add_len] = 0;
STR_ASSERT(Owned);
return add_len;
}
int Str::append_from(int idx, const char* s, const char* s_end)
{
if (!s_end)
s_end = s + strlen(s);
int add_len = (int)(s_end - s);
if (Capacity < idx + add_len + 1)
reserve(idx + add_len + 1);
memcpy(Data + idx, (const void*)s, (size_t)add_len);
Data[idx + add_len] = 0; // Our source data isn't necessarily zero-terminated
STR_ASSERT(Owned);
return add_len;
}
// FIXME: merge setfv() and appendfv()?
int Str::appendfv_from(int idx, const char* fmt, va_list args)
{
// Needed for portability on platforms where va_list are passed by reference and modified by functions
va_list args2;
va_copy(args2, args);
// MSVC returns -1 on overflow when writing, which forces us to do two passes
// FIXME-OPT: Find a way around that.
#ifdef _MSC_VER
int add_len = vsnprintf(NULL, 0, fmt, args);
STR_ASSERT(add_len >= 0);
if (Capacity < idx + add_len + 1)
reserve(idx + add_len + 1);
add_len = vsnprintf(Data + idx, add_len + 1, fmt, args2);
#else
// First try
int add_len = vsnprintf(Owned ? Data + idx : NULL, Owned ? (size_t)(Capacity - idx) : 0, fmt, args);
STR_ASSERT(add_len >= 0);
if (Capacity < idx + add_len + 1)
{
reserve(idx + add_len + 1);
add_len = vsnprintf(Data + idx, (size_t)add_len + 1, fmt, args2);
}
#endif
STR_ASSERT(Owned);
return add_len;
}
int Str::appendf_from(int idx, const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
int len = appendfv_from(idx, fmt, args);
va_end(args);
return len;
}
int Str::append(char c)
{
int cur_len = length();
return append_from(cur_len, c);
}
int Str::append(const char* s, const char* s_end)
{
int cur_len = length();
return append_from(cur_len, s, s_end);
}
int Str::appendfv(const char* fmt, va_list args)
{
int cur_len = length();
return appendfv_from(cur_len, fmt, args);
}
int Str::appendf(const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
int len = appendfv(fmt, args);
va_end(args);
return len;
}
#endif // #define STR_IMPLEMENTATION
//-------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff