Repository: LunarG/gfxreconstruct
Pull request #2109: Unit tests trimming
Author: login: locke-lunarg
Created: 2025-04-02T00:28:24Z | Merged: 2025-05-08T14:07:56Z

Add trimming tests.  It needs to set `TRIMMING_FRAMES` in `test_cases\[test_app].cpp`. It could be a frame or a range.  It needs to add a test file to  `test\known_good`.


Diff:
diff --git a/test/known_good/wait-for-present_frame_2.gfxr b/test/known_good/wait-for-present_frame_2.gfxr
new file mode 100644
index 0000000000..a45dbba440
Binary files /dev/null and b/test/known_good/wait-for-present_frame_2.gfxr differ
diff --git a/test/test_cases/wait-for-present.cpp b/test/test_cases/wait-for-present.cpp
index 07049e2fef..d9d7f2aede 100644
--- a/test/test_cases/wait-for-present.cpp
+++ b/test/test_cases/wait-for-present.cpp
@@ -16,7 +16,9 @@ char const* const KNOWN_GFXR_PATH   = "wait-for-present_macos.gfxr";
 char const* const KNOWN_GFXR_PATH   = "wait-for-present.gfxr";
 #endif
 
+char const* const TRIMMING_FRAMES = "2";
+
 TEST(WaitForPresent, CorrectGFXR)
 {
-    verify_gfxr(APP_RUN_DIRECTORY, APP_PATH, KNOWN_GFXR_PATH);
+    verify_gfxr(APP_RUN_DIRECTORY, APP_PATH, KNOWN_GFXR_PATH, TRIMMING_FRAMES);
 }
diff --git a/test/verify-gfxr.cpp b/test/verify-gfxr.cpp
index f600cb0bbe..613287a3d0 100644
--- a/test/verify-gfxr.cpp
+++ b/test/verify-gfxr.cpp
@@ -55,7 +55,70 @@ struct Paths
     std::filesystem::path app_json_path;
     std::filesystem::path known_good_json_path;
 
-    Paths(char const* app_directory, char const* app_executable, char const* known_gfxr_path)
+    std::filesystem::path capture_trimming_path{ base_path };
+    std::filesystem::path known_good_trimming_path{ base_path };
+    std::filesystem::path app_trimming_json_path;
+    std::filesystem::path known_good_trimming_json_path;
+
+    void trimming_paths(char const* app_directory,
+                        char const* known_gfxr_path,
+                        char const* trimming_frames)
+    {
+        // Trimming suffix is like "_frame_10" or "_frames_10_through_100"
+        std::string s_trimming_frames = trimming_frames;
+        std::string trimming_suffix   = "_frame";
+        std::string range_begin       = "";
+        std::string range_end         = "";
+
+        auto index = s_trimming_frames.find("-");
+        if (index == std::string::npos)
+        {
+            range_begin = s_trimming_frames;
+        }
+        else
+        {
+            range_begin = s_trimming_frames.substr(0, index);
+            range_end   = s_trimming_frames.substr(index + 1);
+        }
+
+        if (!range_end.empty())
+        {
+            trimming_suffix += "s";
+        }
+        trimming_suffix += "_";
+        trimming_suffix += range_begin;
+        if (!range_end.empty())
+        {
+            trimming_suffix += "_through_";
+            trimming_suffix += range_end;
+        }
+
+        std::string capture_trimming_file = "actual";
+        capture_trimming_file += trimming_suffix;
+        capture_trimming_file += ".gfxr";
+        capture_trimming_path.append("test_apps");
+        capture_trimming_path.append(app_directory);
+        capture_trimming_path.append(capture_trimming_file);
+
+        std::string s_known_gfxr_path        = known_gfxr_path;
+        index                                = s_known_gfxr_path.find(".gfxr");
+        std::string known_gfxr_trimming_path = s_known_gfxr_path.substr(0, index);
+        known_gfxr_trimming_path += trimming_suffix;
+        known_gfxr_trimming_path += ".gfxr";
+        known_good_trimming_path.append("known_good");
+        known_good_trimming_path.append(known_gfxr_trimming_path);
+
+        app_trimming_json_path = std::filesystem::path{ capture_trimming_path };
+        app_trimming_json_path.replace_extension(".json");
+
+        known_good_trimming_json_path = std::filesystem::path{ known_good_trimming_path };
+        known_good_trimming_json_path.replace_extension(".json");
+    }
+
+    Paths(char const* app_directory,
+          char const* app_executable,
+          char const* known_gfxr_path,
+          char const* trimming_frames)
     {
         full_app_directory.append("test_apps");
         full_app_directory.append(app_directory);
@@ -77,6 +140,62 @@ struct Paths
 
         known_good_json_path = std::filesystem::path{ known_good_path };
         known_good_json_path.replace_extension(".json");
+
+        if (trimming_frames)
+        {
+            trimming_paths(app_directory, known_gfxr_path, trimming_frames);
+        }
+    }
+};
+
+// destructor unsets all env vars. It helps when the user forget to unset to affect the other tests.
+class EnvironmentVariables
+{
+  private:
+    std::unordered_map<std::string, std::string> env_vars;
+
+  public:
+    ~EnvironmentVariables()
+    {
+        for (auto& env_var : env_vars)
+        {
+#if defined(__linux__) || defined(__APPLE__)
+            unsetenv(env_var.first.c_str());
+#elif defined(_WIN32)
+            _putenv_s(env_var.first.c_str(), "");
+#else
+#error "Unsupported platform"
+#endif
+        }
+        env_vars.clear();
+    }
+
+void SetEnv(const char* env_name, const char* env_var)
+    {
+#if defined(__linux__) || defined(__APPLE__)
+        ASSERT_EQ(setenv(env_name, env_var, 1), 0) << "set env var: " << env_name << ": " << env_var << " failed.";
+#elif defined(_WIN32)
+        ASSERT_EQ(_putenv_s(env_name, env_var), 0) << "set env var: " << env_name << ": " << env_var << " failed.";
+#else
+#error "Unsupported platform"
+#endif
+        env_vars.insert(std::pair(env_name, env_var));
+    }
+
+    void UnsetEnv(const char* env_name)
+    {
+#if defined(__linux__) || defined(__APPLE__)
+        ASSERT_EQ(unsetenv(env_name), 0) << "unset env var: " << env_name << " failed.";
+#elif defined(_WIN32)
+        ASSERT_EQ(_putenv_s(env_name, ""), 0) << "unset env var: " << env_name << " failed.";
+#else
+#error "Unsupported platform"
+#endif
+        auto entry = env_vars.find(env_name);
+        if (entry != env_vars.end())
+        {
+            env_vars.erase(entry);
+        }
     }
 };
 
@@ -101,14 +220,53 @@ int run_command(std::filesystem::path const& working_directory,
 
 void run_in_background(char const* app_directory, char const* app_executable)
 {
-    Paths paths{ app_directory, app_executable, "" };
+    Paths paths{ app_directory, app_executable, "", nullptr };
     run_command(paths.full_app_directory, paths.full_executable_path, { "&" });
 }
 
-void verify_gfxr(char const* app_directory, char const* app_executable, char const* known_gfxr_path)
+void run_trimming_app(const Paths& paths, char const* trimming_frames)
 {
-    Paths paths{ app_directory, app_executable, known_gfxr_path };
+    EnvironmentVariables env_vars;
 
+    // To not affect the other tests, set env var programmatically, and unset it when it isn't needed.
+    env_vars.SetEnv("GFXRECON_CAPTURE_FRAMES", trimming_frames);
+
+    auto result = run_command(paths.full_app_directory, paths.full_executable_path, {});
+    ASSERT_EQ(result, 0) << "trimming command failed " << paths.full_executable_path << " in path "
+                         << paths.full_app_directory;
+
+    env_vars.UnsetEnv("GFXRECON_CAPTURE_FRAMES");
+
+    // convert actual gfxr
+    result = run_command(paths.base_path, paths.convert_path, { paths.capture_trimming_path.string() });
+    ASSERT_EQ(result, 0) << "trimming command failed " << paths.convert_path << " " << paths.capture_trimming_path
+                         << " in path " << paths.base_path;
+
+    // convert known good gfxr
+    result = run_command(paths.base_path, paths.convert_path, { paths.known_good_trimming_path.string() });
+    ASSERT_EQ(result, 0) << "trimming command failed " << paths.convert_path << " " << paths.known_good_trimming_path
+                         << " in path " << paths.base_path;
+
+    std::ifstream app_trimming_file{ paths.app_trimming_json_path };
+    ASSERT_TRUE(app_trimming_file.is_open())
+        << "app trimming json file: " << paths.app_trimming_json_path << " would not open";
+    auto app_trimming_json = nlohmann::json::parse(app_trimming_file, clean_gfxr_json);
+
+    std::ifstream known_trimming_file{ paths.known_good_trimming_json_path };
+    ASSERT_TRUE(known_trimming_file.is_open())
+        << "known good trimming json file: " << paths.known_good_trimming_json_path << " would not open ";
+    auto known_trimming_json = nlohmann::json::parse(known_trimming_file, clean_gfxr_json);
+
+    auto trimming_diff = nlohmann::json::diff(known_trimming_json, app_trimming_json);
+    ASSERT_EQ(trimming_diff.size(), 0) << std::setw(4) << trimming_diff;
+}
+
+void verify_gfxr(char const* app_directory,
+                 char const* app_executable,
+                 char const* known_gfxr_path,
+                 char const* trimming_frames)
+{
+    Paths paths{ app_directory, app_executable, known_gfxr_path, trimming_frames };
     int result;
 
     // run app
@@ -126,13 +284,18 @@ void verify_gfxr(char const* app_directory, char const* app_executable, char con
                          << paths.base_path;
 
     std::ifstream app_file{ paths.app_json_path };
-    ASSERT_TRUE(app_file.is_open()) << "app json file would not open";
+    ASSERT_TRUE(app_file.is_open()) << "app json file: " << paths.app_json_path << " would not open";
     auto app_json = nlohmann::json::parse(app_file, clean_gfxr_json);
 
     std::ifstream known_file{ paths.known_good_json_path };
-    ASSERT_TRUE(known_file.is_open()) << "known good json file would not open";
+    ASSERT_TRUE(known_file.is_open()) << "known good json file: " << paths.known_good_json_path << " would not open";
     auto known_json = nlohmann::json::parse(known_file, clean_gfxr_json);
 
     auto diff = nlohmann::json::diff(known_json, app_json);
     ASSERT_EQ(diff.size(), 0) << std::setw(4) << diff;
+
+    if (trimming_frames)
+    {
+        run_trimming_app(paths, trimming_frames);
+    }
 }
diff --git a/test/verify-gfxr.h b/test/verify-gfxr.h
index 378c5efce9..26ea26d3e6 100644
--- a/test/verify-gfxr.h
+++ b/test/verify-gfxr.h
@@ -9,7 +9,8 @@ void run_in_background(char const* app_directory, char const* app_executable);
  * app_run_directory - the directory to run the application from,  relative to test_apps folder
  * app_path          - the path to the application, relative to app_run_directory
  * known_gfxr_path   - path to the known good gfxr file, relative to the known_good folder
- *
+ * trimming_frames   - It's not nullptr if it needs to run trimming app. frame info ex: "10" or "10-100"
+ * 
  * expects the following environment variables to be set
  * VK_LAYER_PATH     - path to VkLayer_gfxreconstruct.dll
  * VK_DRIVER_FILES   - path to VkICD_mock_icd.json
@@ -19,6 +20,9 @@ void run_in_background(char const* app_directory, char const* app_executable);
  * GFXRECON_CAPTURE_FILE_TIMESTAMP=false
  * GFXRECON_CAPTURE_FILE=actual.gfxr
  */
-void verify_gfxr(char const* app_directory, char const* app_executable, char const* known_gfxr_path);
+void verify_gfxr(char const* app_directory,
+                 char const* app_executable,
+                 char const* known_gfxr_path,
+                 char const* trimming_frames = nullptr);
 
 #endif // GFXRECONSTRUCT_VERIFY_GFXR_H


Reviews:
user: login: antonio-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T09:54:44Z
user: login: locke-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T22:59:11Z
user: login: locke-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T22:59:24Z
user: login: locke-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T22:59:28Z
user: login: locke-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T22:59:32Z
user: login: locke-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T22:59:36Z
user: login: locke-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T22:59:40Z
user: login: locke-lunarg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T22:59:44Z
user: login: antonio-lunarg
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T06:55:12Z

Review comments:
diff_hunk: @@ -135,4 +245,38 @@ void verify_gfxr(char const* app_directory, char const* app_executable, char con
 
     auto diff = nlohmann::json::diff(known_json, app_json);
     ASSERT_EQ(diff.size(), 0) << std::setw(4) << diff;
+
+    if (trimming_frames)
+    {
+        // run app
+        // To not affect the other tests, set env var programmatically, and unset it when it isn't needed.
path: test/verify-gfxr.cpp
user: login: antonio-lunarg
body: I like this RAII approach with env vars, and it's going to be useful if we plan to move env vars setup from `run-tests.sh` to the test-runner.
created_at: 2025-05-07T09:43:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -135,4 +245,38 @@ void verify_gfxr(char const* app_directory, char const* app_executable, char con
 
     auto diff = nlohmann::json::diff(known_json, app_json);
     ASSERT_EQ(diff.size(), 0) << std::setw(4) << diff;
+
+    if (trimming_frames)
+    {
path: test/verify-gfxr.cpp
user: login: antonio-lunarg
body: nit: I would put this in a separate function.
created_at: 2025-05-07T09:45:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -135,4 +245,38 @@ void verify_gfxr(char const* app_directory, char const* app_executable, char con
 
     auto diff = nlohmann::json::diff(known_json, app_json);
     ASSERT_EQ(diff.size(), 0) << std::setw(4) << diff;
+
+    if (trimming_frames)
+    {
+        // run app
+        // To not affect the other tests, set env var programmatically, and unset it when it isn't needed.
+        env_vars.SetEnv("GFXRECON_CAPTURE_FRAMES", trimming_frames);
+
+        result = run_command(paths.full_app_directory, paths.full_executable_path, {});
+        ASSERT_EQ(result, 0) << "trimming command failed " << paths.full_executable_path << " in path "
+                             << paths.full_app_directory;
+
+        env_vars.UnsetEnv("GFXRECON_CAPTURE_FRAMES");
+
+        // convert actual gfxr
+        result = run_command(paths.base_path, paths.convert_path, { paths.capture_trimming_path.string() });
+        ASSERT_EQ(result, 0) << "trimming command failed " << paths.convert_path << " " << paths.capture_trimming_path
+                             << " in path " << paths.base_path;
+
+        // convert known good gfxr
+        result = run_command(paths.base_path, paths.convert_path, { paths.known_good_trimming_path.string() });
+        ASSERT_EQ(result, 0) << "trimming command failed " << paths.convert_path << " "
+                             << paths.known_good_trimming_path << " in path " << paths.base_path;
+
+        std::ifstream app_trimming_file{ paths.app_trimming_json_path };
+        ASSERT_TRUE(app_trimming_file.is_open()) << "app trimming json file would not open";
path: test/verify-gfxr.cpp
user: login: antonio-lunarg
body: Could you add `paths.app_trimming_json_path` to the error message?
created_at: 2025-05-07T09:46:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -135,4 +245,38 @@ void verify_gfxr(char const* app_directory, char const* app_executable, char con
 
     auto diff = nlohmann::json::diff(known_json, app_json);
     ASSERT_EQ(diff.size(), 0) << std::setw(4) << diff;
+
+    if (trimming_frames)
+    {
+        // run app
+        // To not affect the other tests, set env var programmatically, and unset it when it isn't needed.
+        env_vars.SetEnv("GFXRECON_CAPTURE_FRAMES", trimming_frames);
+
+        result = run_command(paths.full_app_directory, paths.full_executable_path, {});
+        ASSERT_EQ(result, 0) << "trimming command failed " << paths.full_executable_path << " in path "
+                             << paths.full_app_directory;
+
+        env_vars.UnsetEnv("GFXRECON_CAPTURE_FRAMES");
+
+        // convert actual gfxr
+        result = run_command(paths.base_path, paths.convert_path, { paths.capture_trimming_path.string() });
+        ASSERT_EQ(result, 0) << "trimming command failed " << paths.convert_path << " " << paths.capture_trimming_path
+                             << " in path " << paths.base_path;
+
+        // convert known good gfxr
+        result = run_command(paths.base_path, paths.convert_path, { paths.known_good_trimming_path.string() });
+        ASSERT_EQ(result, 0) << "trimming command failed " << paths.convert_path << " "
+                             << paths.known_good_trimming_path << " in path " << paths.base_path;
+
+        std::ifstream app_trimming_file{ paths.app_trimming_json_path };
+        ASSERT_TRUE(app_trimming_file.is_open()) << "app trimming json file would not open";
+        auto app_trimming_json = nlohmann::json::parse(app_trimming_file, clean_gfxr_json);
+
+        std::ifstream known_trimming_file{ paths.known_good_trimming_json_path };
+        ASSERT_TRUE(known_trimming_file.is_open()) << "known good trimming json file would not open";
path: test/verify-gfxr.cpp
user: login: antonio-lunarg
body: Could you add `paths.known_good_trimming_json_path` to the error message?
created_at: 2025-05-07T09:46:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -19,6 +19,9 @@ void run_in_background(char const* app_directory, char const* app_executable);
  * GFXRECON_CAPTURE_FILE_TIMESTAMP=false
  * GFXRECON_CAPTURE_FILE=actual.gfxr
  */
-void verify_gfxr(char const* app_directory, char const* app_executable, char const* known_gfxr_path);
+void verify_gfxr(char const* app_directory,
+                 char const* app_executable,
+                 char const* known_gfxr_path,
+                 char const* known_gfxr_trimming_path = nullptr);
path: test/verify-gfxr.h
user: login: antonio-lunarg
body: Can you mention the `known_gfxr_trimming_path` parameter in the documentation section above?
created_at: 2025-05-07T09:47:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -77,6 +85,104 @@ struct Paths
 
         known_good_json_path = std::filesystem::path{ known_good_path };
         known_good_json_path.replace_extension(".json");
+
+        if (trimming_frames)
+        {
+            // Trimming suffix is like "_frame_10" or "_frames_10_through_100"
+            std::string s_trimming_frames = trimming_frames;
+            std::string trimming_suffix   = "_frame";
+            std::string range_1           = "";
+            std::string range_2           = "";
+
+            auto index = s_trimming_frames.find("-");
+            if (index == std::string::npos)
+            {
+                range_1 = s_trimming_frames;
+            }
+            else
+            {
+                range_1 = s_trimming_frames.substr(0, index);
+                range_2 = s_trimming_frames.substr(index + 1);
+            }
+
+            if (!range_2.empty())
+            {
+                trimming_suffix += "s";
+            }
+            trimming_suffix += "_";
+            trimming_suffix += range_1;
+            if (!range_2.empty())
+            {
+                trimming_suffix += "_through_";
+                trimming_suffix += range_2;
+            }
+
+            std::string capture_trimming_file = "actual";
+            capture_trimming_file += trimming_suffix;
+            capture_trimming_file += ".gfxr";
+            capture_trimming_path.append("test_apps");
+            capture_trimming_path.append(app_directory);
+            capture_trimming_path.append(capture_trimming_file);
+
+            std::string s_known_gfxr_path        = known_gfxr_path;
+            index                                = s_known_gfxr_path.find(".gfxr");
+            std::string known_gfxr_trimming_path = s_known_gfxr_path.substr(0, index);
+            known_gfxr_trimming_path += trimming_suffix;
+            known_gfxr_trimming_path += ".gfxr";
+            known_good_trimming_path.append("known_good");
+            known_good_trimming_path.append(known_gfxr_trimming_path);
+
+            app_trimming_json_path = std::filesystem::path{ capture_trimming_path };
+            app_trimming_json_path.replace_extension(".json");
+
+            known_good_trimming_json_path = std::filesystem::path{ known_good_trimming_path };
+            known_good_trimming_json_path.replace_extension(".json");
+        }
+    }
+};
+
+// destructor unsets all env vars. It helps when the user forget to unset to affect the other tests.
+class EnvironmentVariables
+{
+  private:
+    std::unordered_map<std::string, std::string> env_vars;
+
+  public:
+    ~EnvironmentVariables()
+    {
+        for (auto& env_var : env_vars)
+        {
+#if defined(__linux__) || defined(__APPLE__)
+            unsetenv(env_var.first.c_str());
+#elif defined(_WIN32)
+            _putenv_s(env_var.first.c_str(), "");
+#endif
+        }
+        env_vars.clear();
+    }
+
+void SetEnv(const char* env_name, const char* env_var)
+    {
+#if defined(__linux__) || defined(__APPLE__)
+        ASSERT_EQ(setenv(env_name, env_var, 1), 0) << "set env var: " << env_name << ": " << env_var << " failed.";
+#elif defined(_WIN32)
+        ASSERT_EQ(_putenv_s(env_name, env_var), 0) << "set env var: " << env_name << ": " << env_var << " failed.";
+#endif
path: test/verify-gfxr.cpp
user: login: antonio-lunarg
body: nit: I would add this case:
```c
#else
#error "Unsupported platform"
#endif
```
created_at: 2025-05-07T09:49:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -77,6 +85,104 @@ struct Paths
 
         known_good_json_path = std::filesystem::path{ known_good_path };
         known_good_json_path.replace_extension(".json");
+
+        if (trimming_frames)
+        {
+            // Trimming suffix is like "_frame_10" or "_frames_10_through_100"
+            std::string s_trimming_frames = trimming_frames;
+            std::string trimming_suffix   = "_frame";
+            std::string range_1           = "";
+            std::string range_2           = "";
+
+            auto index = s_trimming_frames.find("-");
+            if (index == std::string::npos)
+            {
+                range_1 = s_trimming_frames;
+            }
+            else
+            {
+                range_1 = s_trimming_frames.substr(0, index);
+                range_2 = s_trimming_frames.substr(index + 1);
+            }
+
+            if (!range_2.empty())
+            {
+                trimming_suffix += "s";
+            }
+            trimming_suffix += "_";
+            trimming_suffix += range_1;
+            if (!range_2.empty())
+            {
+                trimming_suffix += "_through_";
+                trimming_suffix += range_2;
+            }
+
+            std::string capture_trimming_file = "actual";
+            capture_trimming_file += trimming_suffix;
+            capture_trimming_file += ".gfxr";
+            capture_trimming_path.append("test_apps");
+            capture_trimming_path.append(app_directory);
+            capture_trimming_path.append(capture_trimming_file);
+
+            std::string s_known_gfxr_path        = known_gfxr_path;
+            index                                = s_known_gfxr_path.find(".gfxr");
+            std::string known_gfxr_trimming_path = s_known_gfxr_path.substr(0, index);
+            known_gfxr_trimming_path += trimming_suffix;
+            known_gfxr_trimming_path += ".gfxr";
+            known_good_trimming_path.append("known_good");
+            known_good_trimming_path.append(known_gfxr_trimming_path);
+
+            app_trimming_json_path = std::filesystem::path{ capture_trimming_path };
+            app_trimming_json_path.replace_extension(".json");
+
+            known_good_trimming_json_path = std::filesystem::path{ known_good_trimming_path };
+            known_good_trimming_json_path.replace_extension(".json");
+        }
+    }
+};
+
+// destructor unsets all env vars. It helps when the user forget to unset to affect the other tests.
+class EnvironmentVariables
+{
+  private:
+    std::unordered_map<std::string, std::string> env_vars;
+
+  public:
+    ~EnvironmentVariables()
+    {
+        for (auto& env_var : env_vars)
+        {
+#if defined(__linux__) || defined(__APPLE__)
+            unsetenv(env_var.first.c_str());
+#elif defined(_WIN32)
+            _putenv_s(env_var.first.c_str(), "");
+#endif
path: test/verify-gfxr.cpp
user: login: antonio-lunarg
body: nit: I would add this case:
```c
#else
#error "Unsupported platform"
#endif
```
created_at: 2025-05-07T09:50:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -77,6 +85,104 @@ struct Paths
 
         known_good_json_path = std::filesystem::path{ known_good_path };
         known_good_json_path.replace_extension(".json");
+
+        if (trimming_frames)
+        {
+            // Trimming suffix is like "_frame_10" or "_frames_10_through_100"
+            std::string s_trimming_frames = trimming_frames;
+            std::string trimming_suffix   = "_frame";
+            std::string range_1           = "";
+            std::string range_2           = "";
path: test/verify-gfxr.cpp
user: login: antonio-lunarg
body: nit: I would call these `range_begin` and `range_end`
created_at: 2025-05-07T09:51:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -135,4 +245,38 @@ void verify_gfxr(char const* app_directory, char const* app_executable, char con
 
     auto diff = nlohmann::json::diff(known_json, app_json);
     ASSERT_EQ(diff.size(), 0) << std::setw(4) << diff;
+
+    if (trimming_frames)
+    {
path: test/verify-gfxr.cpp
user: login: locke-lunarg
body: fixed
created_at: 2025-05-07T22:59:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -135,4 +245,38 @@ void verify_gfxr(char const* app_directory, char const* app_executable, char con
 
     auto diff = nlohmann::json::diff(known_json, app_json);
     ASSERT_EQ(diff.size(), 0) << std::setw(4) << diff;
+
+    if (trimming_frames)
+    {
+        // run app
+        // To not affect the other tests, set env var programmatically, and unset it when it isn't needed.
+        env_vars.SetEnv("GFXRECON_CAPTURE_FRAMES", trimming_frames);
+
+        result = run_command(paths.full_app_directory, paths.full_executable_path, {});
+        ASSERT_EQ(result, 0) << "trimming command failed " << paths.full_executable_path << " in path "
+                             << paths.full_app_directory;
+
+        env_vars.UnsetEnv("GFXRECON_CAPTURE_FRAMES");
+
+        // convert actual gfxr
+        result = run_command(paths.base_path, paths.convert_path, { paths.capture_trimming_path.string() });
+        ASSERT_EQ(result, 0) << "trimming command failed " << paths.convert_path << " " << paths.capture_trimming_path
+                             << " in path " << paths.base_path;
+
+        // convert known good gfxr
+        result = run_command(paths.base_path, paths.convert_path, { paths.known_good_trimming_path.string() });
+        ASSERT_EQ(result, 0) << "trimming command failed " << paths.convert_path << " "
+                             << paths.known_good_trimming_path << " in path " << paths.base_path;
+
+        std::ifstream app_trimming_file{ paths.app_trimming_json_path };
+        ASSERT_TRUE(app_trimming_file.is_open()) << "app trimming json file would not open";
path: test/verify-gfxr.cpp
user: login: locke-lunarg
body: fixed
created_at: 2025-05-07T22:59:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -135,4 +245,38 @@ void verify_gfxr(char const* app_directory, char const* app_executable, char con
 
     auto diff = nlohmann::json::diff(known_json, app_json);
     ASSERT_EQ(diff.size(), 0) << std::setw(4) << diff;
+
+    if (trimming_frames)
+    {
+        // run app
+        // To not affect the other tests, set env var programmatically, and unset it when it isn't needed.
+        env_vars.SetEnv("GFXRECON_CAPTURE_FRAMES", trimming_frames);
+
+        result = run_command(paths.full_app_directory, paths.full_executable_path, {});
+        ASSERT_EQ(result, 0) << "trimming command failed " << paths.full_executable_path << " in path "
+                             << paths.full_app_directory;
+
+        env_vars.UnsetEnv("GFXRECON_CAPTURE_FRAMES");
+
+        // convert actual gfxr
+        result = run_command(paths.base_path, paths.convert_path, { paths.capture_trimming_path.string() });
+        ASSERT_EQ(result, 0) << "trimming command failed " << paths.convert_path << " " << paths.capture_trimming_path
+                             << " in path " << paths.base_path;
+
+        // convert known good gfxr
+        result = run_command(paths.base_path, paths.convert_path, { paths.known_good_trimming_path.string() });
+        ASSERT_EQ(result, 0) << "trimming command failed " << paths.convert_path << " "
+                             << paths.known_good_trimming_path << " in path " << paths.base_path;
+
+        std::ifstream app_trimming_file{ paths.app_trimming_json_path };
+        ASSERT_TRUE(app_trimming_file.is_open()) << "app trimming json file would not open";
+        auto app_trimming_json = nlohmann::json::parse(app_trimming_file, clean_gfxr_json);
+
+        std::ifstream known_trimming_file{ paths.known_good_trimming_json_path };
+        ASSERT_TRUE(known_trimming_file.is_open()) << "known good trimming json file would not open";
path: test/verify-gfxr.cpp
user: login: locke-lunarg
body: fixed
created_at: 2025-05-07T22:59:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -19,6 +19,9 @@ void run_in_background(char const* app_directory, char const* app_executable);
  * GFXRECON_CAPTURE_FILE_TIMESTAMP=false
  * GFXRECON_CAPTURE_FILE=actual.gfxr
  */
-void verify_gfxr(char const* app_directory, char const* app_executable, char const* known_gfxr_path);
+void verify_gfxr(char const* app_directory,
+                 char const* app_executable,
+                 char const* known_gfxr_path,
+                 char const* known_gfxr_trimming_path = nullptr);
path: test/verify-gfxr.h
user: login: locke-lunarg
body: fixed
created_at: 2025-05-07T22:59:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -77,6 +85,104 @@ struct Paths
 
         known_good_json_path = std::filesystem::path{ known_good_path };
         known_good_json_path.replace_extension(".json");
+
+        if (trimming_frames)
+        {
+            // Trimming suffix is like "_frame_10" or "_frames_10_through_100"
+            std::string s_trimming_frames = trimming_frames;
+            std::string trimming_suffix   = "_frame";
+            std::string range_1           = "";
+            std::string range_2           = "";
+
+            auto index = s_trimming_frames.find("-");
+            if (index == std::string::npos)
+            {
+                range_1 = s_trimming_frames;
+            }
+            else
+            {
+                range_1 = s_trimming_frames.substr(0, index);
+                range_2 = s_trimming_frames.substr(index + 1);
+            }
+
+            if (!range_2.empty())
+            {
+                trimming_suffix += "s";
+            }
+            trimming_suffix += "_";
+            trimming_suffix += range_1;
+            if (!range_2.empty())
+            {
+                trimming_suffix += "_through_";
+                trimming_suffix += range_2;
+            }
+
+            std::string capture_trimming_file = "actual";
+            capture_trimming_file += trimming_suffix;
+            capture_trimming_file += ".gfxr";
+            capture_trimming_path.append("test_apps");
+            capture_trimming_path.append(app_directory);
+            capture_trimming_path.append(capture_trimming_file);
+
+            std::string s_known_gfxr_path        = known_gfxr_path;
+            index                                = s_known_gfxr_path.find(".gfxr");
+            std::string known_gfxr_trimming_path = s_known_gfxr_path.substr(0, index);
+            known_gfxr_trimming_path += trimming_suffix;
+            known_gfxr_trimming_path += ".gfxr";
+            known_good_trimming_path.append("known_good");
+            known_good_trimming_path.append(known_gfxr_trimming_path);
+
+            app_trimming_json_path = std::filesystem::path{ capture_trimming_path };
+            app_trimming_json_path.replace_extension(".json");
+
+            known_good_trimming_json_path = std::filesystem::path{ known_good_trimming_path };
+            known_good_trimming_json_path.replace_extension(".json");
+        }
+    }
+};
+
+// destructor unsets all env vars. It helps when the user forget to unset to affect the other tests.
+class EnvironmentVariables
+{
+  private:
+    std::unordered_map<std::string, std::string> env_vars;
+
+  public:
+    ~EnvironmentVariables()
+    {
+        for (auto& env_var : env_vars)
+        {
+#if defined(__linux__) || defined(__APPLE__)
+            unsetenv(env_var.first.c_str());
+#elif defined(_WIN32)
+            _putenv_s(env_var.first.c_str(), "");
+#endif
+        }
+        env_vars.clear();
+    }
+
+void SetEnv(const char* env_name, const char* env_var)
+    {
+#if defined(__linux__) || defined(__APPLE__)
+        ASSERT_EQ(setenv(env_name, env_var, 1), 0) << "set env var: " << env_name << ": " << env_var << " failed.";
+#elif defined(_WIN32)
+        ASSERT_EQ(_putenv_s(env_name, env_var), 0) << "set env var: " << env_name << ": " << env_var << " failed.";
+#endif
path: test/verify-gfxr.cpp
user: login: locke-lunarg
body: fixed
created_at: 2025-05-07T22:59:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -77,6 +85,104 @@ struct Paths
 
         known_good_json_path = std::filesystem::path{ known_good_path };
         known_good_json_path.replace_extension(".json");
+
+        if (trimming_frames)
+        {
+            // Trimming suffix is like "_frame_10" or "_frames_10_through_100"
+            std::string s_trimming_frames = trimming_frames;
+            std::string trimming_suffix   = "_frame";
+            std::string range_1           = "";
+            std::string range_2           = "";
+
+            auto index = s_trimming_frames.find("-");
+            if (index == std::string::npos)
+            {
+                range_1 = s_trimming_frames;
+            }
+            else
+            {
+                range_1 = s_trimming_frames.substr(0, index);
+                range_2 = s_trimming_frames.substr(index + 1);
+            }
+
+            if (!range_2.empty())
+            {
+                trimming_suffix += "s";
+            }
+            trimming_suffix += "_";
+            trimming_suffix += range_1;
+            if (!range_2.empty())
+            {
+                trimming_suffix += "_through_";
+                trimming_suffix += range_2;
+            }
+
+            std::string capture_trimming_file = "actual";
+            capture_trimming_file += trimming_suffix;
+            capture_trimming_file += ".gfxr";
+            capture_trimming_path.append("test_apps");
+            capture_trimming_path.append(app_directory);
+            capture_trimming_path.append(capture_trimming_file);
+
+            std::string s_known_gfxr_path        = known_gfxr_path;
+            index                                = s_known_gfxr_path.find(".gfxr");
+            std::string known_gfxr_trimming_path = s_known_gfxr_path.substr(0, index);
+            known_gfxr_trimming_path += trimming_suffix;
+            known_gfxr_trimming_path += ".gfxr";
+            known_good_trimming_path.append("known_good");
+            known_good_trimming_path.append(known_gfxr_trimming_path);
+
+            app_trimming_json_path = std::filesystem::path{ capture_trimming_path };
+            app_trimming_json_path.replace_extension(".json");
+
+            known_good_trimming_json_path = std::filesystem::path{ known_good_trimming_path };
+            known_good_trimming_json_path.replace_extension(".json");
+        }
+    }
+};
+
+// destructor unsets all env vars. It helps when the user forget to unset to affect the other tests.
+class EnvironmentVariables
+{
+  private:
+    std::unordered_map<std::string, std::string> env_vars;
+
+  public:
+    ~EnvironmentVariables()
+    {
+        for (auto& env_var : env_vars)
+        {
+#if defined(__linux__) || defined(__APPLE__)
+            unsetenv(env_var.first.c_str());
+#elif defined(_WIN32)
+            _putenv_s(env_var.first.c_str(), "");
+#endif
path: test/verify-gfxr.cpp
user: login: locke-lunarg
body: fixed
created_at: 2025-05-07T22:59:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -77,6 +85,104 @@ struct Paths
 
         known_good_json_path = std::filesystem::path{ known_good_path };
         known_good_json_path.replace_extension(".json");
+
+        if (trimming_frames)
+        {
+            // Trimming suffix is like "_frame_10" or "_frames_10_through_100"
+            std::string s_trimming_frames = trimming_frames;
+            std::string trimming_suffix   = "_frame";
+            std::string range_1           = "";
+            std::string range_2           = "";
path: test/verify-gfxr.cpp
user: login: locke-lunarg
body: fixed
created_at: 2025-05-07T22:59:44Z
author_association: CONTRIBUTOR

PR comments:
user: login: ci-tester-lunarg
created_at: 2025-04-02T00:28:27Z
body: CI gfxreconstruct build queued with queue ID 410255.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-02T01:30:53Z
body: CI gfxreconstruct build # 6482 running.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-02T02:05:02Z
body: CI gfxreconstruct build # 6482 passed.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-02T03:09:32Z
body: CI gfxreconstruct build queued with queue ID 410368.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-02T03:25:59Z
body: CI gfxreconstruct build queued with queue ID 410389.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-02T04:25:36Z
body: CI gfxreconstruct build queued with queue ID 410417.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-02T04:27:14Z
body: CI gfxreconstruct build queued with queue ID 410418.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-02T04:27:53Z
body: CI gfxreconstruct build queued with queue ID 410419.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-02T06:57:40Z
body: CI gfxreconstruct build # 6489 running.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-02T07:30:04Z
body: CI gfxreconstruct build # 6489 passed.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-02T20:35:06Z
body: CI gfxreconstruct build queued with queue ID 411316.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-02T20:37:42Z
body: CI gfxreconstruct build queued with queue ID 411317.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-02T20:42:24Z
body: CI gfxreconstruct build queued with queue ID 411319.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-02T21:33:20Z
body: CI gfxreconstruct build queued with queue ID 411346.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-02T23:15:01Z
body: CI gfxreconstruct build # 6508 running.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-02T23:47:38Z
body: CI gfxreconstruct build # 6508 passed.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-03T23:11:12Z
body: CI gfxreconstruct build queued with queue ID 412731.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-04T01:28:19Z
body: CI gfxreconstruct build # 6541 running.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-04T02:11:44Z
body: CI gfxreconstruct build # 6541 passed.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-05T00:07:29Z
body: CI gfxreconstruct build queued with queue ID 413821.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-05T00:10:01Z
body: CI gfxreconstruct build # 6557 running.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-05T00:40:01Z
body: CI gfxreconstruct build # 6557 passed.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-14T20:54:47Z
body: CI gfxreconstruct build queued with queue ID 421225.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-14T21:09:30Z
body: CI gfxreconstruct build # 6628 running.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-14T21:52:35Z
body: CI gfxreconstruct build # 6628 passed.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-18T03:37:10Z
body: CI gfxreconstruct build queued with queue ID 424184.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-18T03:49:42Z
body: CI gfxreconstruct build queued with queue ID 424201.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-18T04:03:24Z
body: CI gfxreconstruct build queued with queue ID 424221.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-18T06:01:21Z
body: CI gfxreconstruct build # 6673 running.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-18T06:38:02Z
body: CI gfxreconstruct build # 6673 passed.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-18T16:59:30Z
body: CI gfxreconstruct build queued with queue ID 424671.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-18T18:44:41Z
body: CI gfxreconstruct build # 6684 running.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-18T19:21:21Z
body: CI gfxreconstruct build # 6684 passed.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-24T18:36:48Z
body: CI gfxreconstruct build queued with queue ID 429506.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-24T19:36:44Z
body: CI gfxreconstruct build # 6728 running.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-04-24T20:13:29Z
body: CI gfxreconstruct build # 6728 passed.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-05-07T22:56:46Z
body: CI gfxreconstruct build queued with queue ID 439323.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-05-07T23:00:28Z
body: CI gfxreconstruct build queued with queue ID 439325.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-05-08T00:07:30Z
body: CI gfxreconstruct build # 6834 running.
author_association: COLLABORATOR
user: login: ci-tester-lunarg
created_at: 2025-05-08T00:43:40Z
body: CI gfxreconstruct build # 6834 passed.
author_association: COLLABORATOR