Trace Redaction - Expand 'scrub ftrace events' test coverage

1. Added integration test that pushes an actual trace through 'scrub ftrace
   events' and makes sure the allow-listed events are not affected.

2. Remove the old serialization tests and replaces them with tests that:

   a: uses TestConfig's DummyFields to communicate and enfore types
      (e.g. field_sfixed32).

   b: cover each values full domain (e.g. min int 32 to max int 64).

Bug: 318576499
Change-Id: Ib1c936289ab769f0d104a487f28c251408696464
diff --git a/Android.bp b/Android.bp
index 8d476ce..3233f92 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12492,6 +12492,7 @@
     srcs: [
         "src/trace_redaction/find_package_uid.cc",
         "src/trace_redaction/populate_allow_lists.cc",
+        "src/trace_redaction/proto_util.cc",
         "src/trace_redaction/prune_package_list.cc",
         "src/trace_redaction/scrub_ftrace_events.cc",
         "src/trace_redaction/scrub_trace_packet.cc",
@@ -12505,6 +12506,7 @@
     name: "perfetto_src_trace_redaction_unittests",
     srcs: [
         "src/trace_redaction/find_package_uid_unittest.cc",
+        "src/trace_redaction/proto_util_unittest.cc",
         "src/trace_redaction/prune_package_list_unittest.cc",
         "src/trace_redaction/scrub_ftrace_events_unittest.cc",
         "src/trace_redaction/scrub_trace_packet_unittest.cc",
diff --git a/src/trace_redaction/BUILD.gn b/src/trace_redaction/BUILD.gn
index 7b2f4c5..b662902 100644
--- a/src/trace_redaction/BUILD.gn
+++ b/src/trace_redaction/BUILD.gn
@@ -32,6 +32,8 @@
     "find_package_uid.h",
     "populate_allow_lists.cc",
     "populate_allow_lists.h",
+    "proto_util.cc",
+    "proto_util.h",
     "prune_package_list.cc",
     "prune_package_list.h",
     "scrub_ftrace_events.cc",
@@ -60,7 +62,10 @@
 
 source_set("integrationtests") {
   testonly = true
-  sources = [ "trace_redactor_integrationtest.cc" ]
+  sources = [
+    "scrub_ftrace_events_integrationtest.cc",
+    "trace_redactor_integrationtest.cc",
+  ]
   deps = [
     ":trace_redaction",
     "../../gn:default_deps",
@@ -77,6 +82,7 @@
   testonly = true
   sources = [
     "find_package_uid_unittest.cc",
+    "proto_util_unittest.cc",
     "prune_package_list_unittest.cc",
     "scrub_ftrace_events_unittest.cc",
     "scrub_trace_packet_unittest.cc",
@@ -86,6 +92,8 @@
     "../../gn:default_deps",
     "../../gn:gtest_and_gmock",
     "../../include/perfetto/protozero:protozero",
+    "../../protos/perfetto/config:cpp",
+    "../../protos/perfetto/config:zero",
     "../../protos/perfetto/trace:non_minimal_cpp",
     "../../protos/perfetto/trace:zero",
     "../../protos/perfetto/trace/android:cpp",
diff --git a/src/trace_redaction/proto_util.cc b/src/trace_redaction/proto_util.cc
new file mode 100644
index 0000000..30962d7
--- /dev/null
+++ b/src/trace_redaction/proto_util.cc
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_redaction/proto_util.h"
+
+#include "perfetto/protozero/field.h"
+#include "perfetto/protozero/message.h"
+
+namespace perfetto::trace_redaction {
+namespace proto_util {
+
+// This is copied from "src/protozero/field.cc", but was modified to use the
+// serialization methods provided in "perfetto/protozero/message.h".
+void AppendField(const protozero::Field& field, protozero::Message* message) {
+  auto id = field.id();
+  auto type = field.type();
+
+  switch (type) {
+    case protozero::proto_utils::ProtoWireType::kVarInt: {
+      message->AppendVarInt(id, field.raw_int_value());
+      return;
+    }
+
+    case protozero::proto_utils::ProtoWireType::kFixed32: {
+      message->AppendFixed(id, field.as_uint32());
+      return;
+    }
+
+    case protozero::proto_utils::ProtoWireType::kFixed64: {
+      message->AppendFixed(id, field.as_uint64());
+      return;
+    }
+
+    case protozero::proto_utils::ProtoWireType::kLengthDelimited: {
+      message->AppendBytes(id, field.data(), field.size());
+      return;
+    }
+  }
+
+  // A switch-statement would be preferred, but when using a switch statement,
+  // it complains that about case coverage.
+  PERFETTO_FATAL("Unknown field type %u", static_cast<uint8_t>(type));
+}
+
+}  // namespace proto_util
+}  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/proto_util.h b/src/trace_redaction/proto_util.h
new file mode 100644
index 0000000..6fac1d8
--- /dev/null
+++ b/src/trace_redaction/proto_util.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_REDACTION_PROTO_UTIL_H_
+#define SRC_TRACE_REDACTION_PROTO_UTIL_H_
+
+#include "perfetto/protozero/field.h"
+#include "perfetto/protozero/message.h"
+
+namespace perfetto::trace_redaction {
+
+// This is here, and not in protozero, because field and message are never found
+// together. Because trace redaction is the only user of this function, it is
+// here.
+namespace proto_util {
+
+void AppendField(const protozero::Field& field, protozero::Message* message);
+
+}  // namespace proto_util
+
+}  // namespace perfetto::trace_redaction
+
+#endif  // SRC_TRACE_REDACTION_PROTO_UTIL_H_
diff --git a/src/trace_redaction/proto_util_unittest.cc b/src/trace_redaction/proto_util_unittest.cc
new file mode 100644
index 0000000..aa5be3a
--- /dev/null
+++ b/src/trace_redaction/proto_util_unittest.cc
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/protozero/scattered_heap_buffer.h"
+
+#include "src/trace_redaction/proto_util.h"
+#include "test/gtest_and_gmock.h"
+
+#include "protos/perfetto/config/test_config.gen.h"
+#include "protos/perfetto/config/test_config.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.gen.h"
+
+namespace perfetto::trace_redaction {
+
+namespace proto_util {
+
+class ProtoUtilTest : public testing::Test {
+ protected:
+  void Reserialize(const protos::gen::TestConfig_DummyFields& fields) {
+    // Serialize the object and then deserialize it with proto decoder. This is
+    // needed to get the fields.
+    auto serialized = fields.SerializeAsString();
+    protozero::ProtoDecoder decoder(serialized);
+
+    protozero::HeapBuffered<protos::pbzero::TestConfig_DummyFields> message;
+
+    for (auto field = decoder.ReadField(); field.valid();
+         field = decoder.ReadField()) {
+      AppendField(field, message.get());
+    }
+
+    auto reserialized = message.SerializeAsString();
+
+    ASSERT_EQ(serialized, reserialized);
+  }
+};
+
+class ProtoUtilUint32Test : public ProtoUtilTest,
+                            public testing::WithParamInterface<uint32_t> {};
+class ProtoUtilUint64Test : public ProtoUtilTest,
+                            public testing::WithParamInterface<uint64_t> {};
+class ProtoUtilInt32Test : public ProtoUtilTest,
+                           public testing::WithParamInterface<int32_t> {};
+class ProtoUtilInt64Test : public ProtoUtilTest,
+                           public testing::WithParamInterface<int64_t> {};
+class ProtoUtilFixed32Test : public ProtoUtilTest,
+                             public testing::WithParamInterface<uint32_t> {};
+class ProtoUtilFixed64Test : public ProtoUtilTest,
+                             public testing::WithParamInterface<uint64_t> {};
+class ProtoUtilSfixed32Test : public ProtoUtilTest,
+                              public testing::WithParamInterface<int32_t> {};
+class ProtoUtilSfixed64Test : public ProtoUtilTest,
+                              public testing::WithParamInterface<int64_t> {};
+class ProtoUtilDoubleTest : public ProtoUtilTest,
+                            public testing::WithParamInterface<double> {};
+class ProtoUtilFloatTest : public ProtoUtilTest,
+                           public testing::WithParamInterface<float> {};
+class ProtoUtilSint32Test : public ProtoUtilTest,
+                            public testing::WithParamInterface<int32_t> {};
+class ProtoUtilSint64Test : public ProtoUtilTest,
+                            public testing::WithParamInterface<int64_t> {};
+class ProtoUtilStringTest : public ProtoUtilTest,
+                            public testing::WithParamInterface<std::string> {};
+class ProtoUtilBytesTest : public ProtoUtilTest,
+                           public testing::WithParamInterface<std::string> {};
+
+TEST_P(ProtoUtilUint32Test, FullDomain) {
+  auto value = GetParam();
+
+  protos::gen::TestConfig_DummyFields fields;
+  fields.set_field_uint32(value);
+  Reserialize(fields);
+}
+
+INSTANTIATE_TEST_SUITE_P(Reserialize,
+                         ProtoUtilUint32Test,
+                         testing::Values(std::numeric_limits<uint32_t>::min(),
+                                         0,
+                                         0xFAAAAAAA,
+                                         std::numeric_limits<uint32_t>::max()));
+
+TEST_P(ProtoUtilUint64Test, FullDomain) {
+  auto value = GetParam();
+
+  protos::gen::TestConfig_DummyFields fields;
+  fields.set_field_uint64(value);
+  Reserialize(fields);
+}
+
+INSTANTIATE_TEST_SUITE_P(Reserialize,
+                         ProtoUtilUint64Test,
+                         testing::Values(std::numeric_limits<uint64_t>::min(),
+                                         0,
+                                         0xFAAAAAAAAAAAAAAA,
+                                         std::numeric_limits<uint64_t>::max()));
+
+TEST_P(ProtoUtilInt32Test, FullDomain) {
+  auto value = GetParam();
+
+  protos::gen::TestConfig_DummyFields fields;
+  fields.set_field_int32(value);
+  Reserialize(fields);
+}
+
+INSTANTIATE_TEST_SUITE_P(Reserialize,
+                         ProtoUtilInt32Test,
+                         testing::Values(std::numeric_limits<int32_t>::min(),
+                                         0xFAAAAAAA,
+                                         0,
+                                         0x0AAAAAAA,
+                                         std::numeric_limits<int32_t>::max()));
+
+TEST_P(ProtoUtilInt64Test, FullDomain) {
+  auto value = GetParam();
+
+  protos::gen::TestConfig_DummyFields fields;
+  fields.set_field_int64(value);
+  Reserialize(fields);
+}
+
+INSTANTIATE_TEST_SUITE_P(Reserialize,
+                         ProtoUtilInt64Test,
+                         testing::Values(std::numeric_limits<int64_t>::min(),
+                                         0xFAAAAAAAAAAAAAAA,
+                                         0,
+                                         0x0AAAAAAAAAAAAAAA,
+                                         std::numeric_limits<int64_t>::max()));
+
+TEST_P(ProtoUtilFixed32Test, FullDomain) {
+  auto value = GetParam();
+
+  protos::gen::TestConfig_DummyFields fields;
+  fields.set_field_fixed32(value);
+  Reserialize(fields);
+}
+
+INSTANTIATE_TEST_SUITE_P(Reserialize,
+                         ProtoUtilFixed32Test,
+                         testing::Values(std::numeric_limits<uint32_t>::min(),
+                                         0,
+                                         0xFAAAAAAAAAAAAAAA,
+                                         std::numeric_limits<uint32_t>::max()));
+
+TEST_P(ProtoUtilSfixed32Test, FullDomain) {
+  auto value = GetParam();
+
+  protos::gen::TestConfig_DummyFields fields;
+  fields.set_field_sfixed32(value);
+  Reserialize(fields);
+}
+
+INSTANTIATE_TEST_SUITE_P(Reserialize,
+                         ProtoUtilSfixed32Test,
+                         testing::Values(std::numeric_limits<int32_t>::min(),
+                                         0xFAAAAAAA,
+                                         0,
+                                         0x0AAAAAAA,
+                                         std::numeric_limits<int32_t>::max()));
+
+TEST_P(ProtoUtilDoubleTest, FullDomain) {
+  auto value = GetParam();
+
+  protos::gen::TestConfig_DummyFields fields;
+  fields.set_field_double(value);
+  Reserialize(fields);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    Reserialize,
+    ProtoUtilDoubleTest,
+    testing::Values(std::numeric_limits<double>::min(),
+                    0.0,
+                    1.0,
+                    std::numeric_limits<double>::infinity(),
+                    std::numeric_limits<double>::max()));
+
+TEST_P(ProtoUtilFloatTest, FullDomain) {
+  auto value = GetParam();
+
+  protos::gen::TestConfig_DummyFields fields;
+  fields.set_field_float(value);
+  Reserialize(fields);
+}
+
+INSTANTIATE_TEST_SUITE_P(Reserialize,
+                         ProtoUtilFloatTest,
+                         testing::Values(std::numeric_limits<float>::min(),
+                                         0.0f,
+                                         1.0f,
+                                         std::numeric_limits<float>::infinity(),
+                                         std::numeric_limits<float>::max()));
+
+TEST_P(ProtoUtilSint64Test, FullDomain) {
+  auto value = GetParam();
+
+  protos::gen::TestConfig_DummyFields fields;
+  fields.set_field_sint64(value);
+  Reserialize(fields);
+}
+
+INSTANTIATE_TEST_SUITE_P(Reserialize,
+                         ProtoUtilSint64Test,
+                         testing::Values(std::numeric_limits<int64_t>::min(),
+                                         0xFAAAAAAAAAAAAAAA,
+                                         0,
+                                         0x0AAAAAAAAAAAAAAA,
+                                         std::numeric_limits<int64_t>::max()));
+
+TEST_P(ProtoUtilSint32Test, FullDomain) {
+  auto value = GetParam();
+
+  protos::gen::TestConfig_DummyFields fields;
+  fields.set_field_sint32(value);
+  Reserialize(fields);
+}
+
+INSTANTIATE_TEST_SUITE_P(Reserialize,
+                         ProtoUtilSint32Test,
+                         testing::Values(std::numeric_limits<int32_t>::min(),
+                                         0xFAAAAAAA,
+                                         0,
+                                         0x0AAAAAAA,
+                                         std::numeric_limits<int32_t>::max()));
+
+TEST_P(ProtoUtilStringTest, Various) {
+  auto value = GetParam();
+
+  protos::gen::TestConfig_DummyFields fields;
+  fields.set_field_string(value);
+  Reserialize(fields);
+}
+
+INSTANTIATE_TEST_SUITE_P(Reserialize,
+                         ProtoUtilStringTest,
+                         testing::Values("",
+                                         "a",
+                                         "abcdefghijklmonpqrstuvwxyz",
+                                         std::string(1024, 'a')));
+
+TEST_P(ProtoUtilBytesTest, Various) {
+  auto value = GetParam();
+
+  protos::gen::TestConfig_DummyFields fields;
+  fields.set_field_bytes(value);
+  Reserialize(fields);
+}
+
+INSTANTIATE_TEST_SUITE_P(Reserialize,
+                         ProtoUtilBytesTest,
+                         testing::Values("",
+                                         "a",
+                                         "abcdefghijklmonpqrstuvwxyz",
+                                         std::string(1024, 'a')));
+
+}  // namespace proto_util
+
+}  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/scrub_ftrace_events.cc b/src/trace_redaction/scrub_ftrace_events.cc
index 67585d1..b41fc6e 100644
--- a/src/trace_redaction/scrub_ftrace_events.cc
+++ b/src/trace_redaction/scrub_ftrace_events.cc
@@ -18,9 +18,8 @@
 
 #include <string>
 
-#include "perfetto/protozero/message.h"
-#include "perfetto/protozero/message_arena.h"
 #include "perfetto/protozero/scattered_heap_buffer.h"
+#include "src/trace_redaction/proto_util.h"
 
 #include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
 #include "protos/perfetto/trace/trace.pbzero.h"
@@ -117,7 +116,7 @@
        packet_child_it = d_packet.ReadField()) {
     // packet.child_not<ftrace_events>( ).do ( ... )
     if (packet_child_it.id() != kFtraceEventsFieldNumber) {
-      AppendField(packet_child_it, packet_msg.get());
+      proto_util::AppendField(packet_child_it, packet_msg.get());
       continue;
     }
 
@@ -132,13 +131,13 @@
          ftrace_events_it = ftrace_events.ReadField()) {
       // packet.child<ftrace_events>( ).child_not<event>( ).do ( ... )
       if (ftrace_events_it.id() != kEventFieldNumber) {
-        AppendField(ftrace_events_it, ftrace_events_msg);
+        proto_util::AppendField(ftrace_events_it, ftrace_events_msg);
         continue;
       }
 
       // packet.child<ftrace_events>( ).child_is<event>( ).do ( ... )
       if (ProbeEvent(context, ftrace_events_it) == Redact::kNothing) {
-        AppendField(ftrace_events_it, ftrace_events_msg);
+        proto_util::AppendField(ftrace_events_it, ftrace_events_msg);
         continue;
       }
 
@@ -150,38 +149,4 @@
   return base::OkStatus();
 }
 
-// This is copied from "src/protozero/field.cc", but was modified to use the
-// serialization methods provided in "perfetto/protozero/message.h".
-void ScrubFtraceEvents::AppendField(const protozero::Field& field,
-                                    protozero::Message* message) {
-  auto id = field.id();
-  auto type = field.type();
-
-  switch (type) {
-    case protozero::proto_utils::ProtoWireType::kVarInt: {
-      message->AppendVarInt(id, field.raw_int_value());
-      return;
-    }
-
-    case protozero::proto_utils::ProtoWireType::kFixed32: {
-      message->AppendFixed(id, field.as_uint32());
-      return;
-    }
-
-    case protozero::proto_utils::ProtoWireType::kFixed64: {
-      message->AppendFixed(id, field.as_uint64());
-      return;
-    }
-
-    case protozero::proto_utils::ProtoWireType::kLengthDelimited: {
-      message->AppendBytes(id, field.data(), field.size());
-      return;
-    }
-  }
-
-  // A switch-statement would be preferred, but when using a switch statement,
-  // it complains that about case coverage.
-  PERFETTO_FATAL("Unknown field type %u", static_cast<uint8_t>(type));
-}
-
 }  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/scrub_ftrace_events.h b/src/trace_redaction/scrub_ftrace_events.h
index 9ae6a98..77e6835 100644
--- a/src/trace_redaction/scrub_ftrace_events.h
+++ b/src/trace_redaction/scrub_ftrace_events.h
@@ -17,8 +17,8 @@
 #ifndef SRC_TRACE_REDACTION_SCRUB_FTRACE_EVENTS_H_
 #define SRC_TRACE_REDACTION_SCRUB_FTRACE_EVENTS_H_
 
-#include "perfetto/protozero/field.h"
-#include "perfetto/protozero/message.h"
+#include <string>
+
 #include "src/trace_redaction/trace_redaction_framework.h"
 
 namespace perfetto::trace_redaction {
@@ -51,10 +51,6 @@
  public:
   base::Status Transform(const Context& context,
                          std::string* packet) const override;
-
-  // Used by `Transform()`. Only exposed for conformance testing.
-  static void AppendField(const protozero::Field& field,
-                          protozero::Message* message);
 };
 
 }  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/scrub_ftrace_events_integrationtest.cc b/src/trace_redaction/scrub_ftrace_events_integrationtest.cc
new file mode 100644
index 0000000..7584b0f
--- /dev/null
+++ b/src/trace_redaction/scrub_ftrace_events_integrationtest.cc
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/file_utils.h"
+#include "src/base/test/status_matchers.h"
+#include "src/base/test/utils.h"
+#include "src/trace_redaction/scrub_ftrace_events.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+#include "test/gtest_and_gmock.h"
+
+#include "protos/perfetto/trace//ftrace/ftrace_event.pbzero.h"
+#include "protos/perfetto/trace/android/packages_list.pbzero.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+#include "protos/perfetto/trace/trace.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto::trace_redaction {
+
+namespace {
+using FtraceEvent = protos::pbzero::FtraceEvent;
+using PackagesList = protos::pbzero::PackagesList;
+using PackageInfo = protos::pbzero::PackagesList::PackageInfo;
+using Trace = protos::pbzero::Trace;
+using TracePacket = protos::pbzero::TracePacket;
+
+constexpr std::string_view kTracePath =
+    "test/data/trace-redaction-general.pftrace";
+
+// Runs ScrubFtraceEvents over an actual trace, verifying packet integrity when
+// fields are removed.
+class ScrubFtraceEventsIntegrationTest : public testing::Test {
+ public:
+  ScrubFtraceEventsIntegrationTest() = default;
+  ~ScrubFtraceEventsIntegrationTest() override = default;
+
+ protected:
+  void SetUp() override {
+    src_trace_ = base::GetTestDataPath(std::string(kTracePath));
+    context_.ftrace_packet_allow_list.insert(
+        protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber);
+  }
+
+  std::string src_trace_;
+
+  Context context_;  // Used for allowlist.
+  ScrubFtraceEvents transform_;
+
+  static base::StatusOr<std::string> ReadRawTrace(const std::string& path) {
+    std::string redacted_buffer;
+
+    if (base::ReadFile(path, &redacted_buffer)) {
+      return redacted_buffer;
+    }
+
+    return base::ErrStatus("Failed to read %s", path.c_str());
+  }
+
+  // Gets spans for `event` messages that contain `sched_switch` messages.
+  static std::vector<protozero::ConstBytes> GetEventsWithSchedSwitch(
+      protos::pbzero::TracePacket::Decoder packet) {
+    std::vector<protozero::ConstBytes> ranges;
+
+    if (!packet.has_ftrace_events()) {
+      return ranges;
+    }
+
+    protos::pbzero::FtraceEventBundle::Decoder bundle(packet.ftrace_events());
+
+    if (!bundle.has_event()) {
+      return ranges;
+    }
+
+    for (auto event_it = bundle.event(); event_it; ++event_it) {
+      protos::pbzero::FtraceEvent::Decoder event(*event_it);
+
+      if (event.has_sched_switch()) {
+        ranges.push_back(*event_it);
+      }
+    }
+
+    return ranges;
+  }
+
+  // Instead of using the allow-list created by PopulateAllowlist, use a simpler
+  // allowlist; an allowlist that contains most value types.
+  //
+  // uint64....FtraceEvent...............timestamp
+  // uint32....FtraceEvent...............pid
+  //
+  // int32.....SchedSwitchFtraceEvent....prev_pid
+  // int64.....SchedSwitchFtraceEvent....prev_state
+  // string....SchedSwitchFtraceEvent....next_comm
+  //
+  // Compare all switch events in each trace. The comparison is only on the
+  // switch packets, not on the data leading up to or around them.
+  static void ComparePackets(protos::pbzero::TracePacket::Decoder left,
+                             protos::pbzero::TracePacket::Decoder right) {
+    auto left_switches = GetEventsWithSchedSwitch(std::move(left));
+    auto right_switches = GetEventsWithSchedSwitch(std::move(right));
+
+    ASSERT_EQ(left_switches.size(), right_switches.size());
+
+    auto left_switch_it = left_switches.begin();
+    auto right_switch_it = right_switches.begin();
+
+    while (left_switch_it != left_switches.end() &&
+           right_switch_it != right_switches.end()) {
+      auto left_switch_str = left_switch_it->ToStdString();
+      auto right_switch_str = right_switch_it->ToStdString();
+
+      ASSERT_EQ(left_switch_str, right_switch_str);
+
+      ++left_switch_it;
+      ++right_switch_it;
+    }
+
+    ASSERT_EQ(left_switches.size(), right_switches.size());
+  }
+};
+
+TEST_F(ScrubFtraceEventsIntegrationTest, FindsPackageAndFiltersPackageList) {
+  const auto& src_file = src_trace_;
+
+  auto raw_src_trace = ReadRawTrace(src_file);
+  ASSERT_OK(raw_src_trace);
+
+  protos::pbzero::Trace::Decoder source_trace(raw_src_trace.value());
+
+  for (auto packet_it = source_trace.packet(); packet_it; ++packet_it) {
+    auto packet = packet_it->as_std_string();
+    ASSERT_OK(transform_.Transform(context_, &packet));
+
+    protos::pbzero::TracePacket::Decoder left_packet(*packet_it);
+    protos::pbzero::TracePacket::Decoder right_packet(packet);
+
+    ComparePackets(std::move(left_packet), std::move(right_packet));
+  }
+}
+
+}  // namespace
+}  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/scrub_ftrace_events_unittest.cc b/src/trace_redaction/scrub_ftrace_events_unittest.cc
index 179c504..5b5012f 100644
--- a/src/trace_redaction/scrub_ftrace_events_unittest.cc
+++ b/src/trace_redaction/scrub_ftrace_events_unittest.cc
@@ -13,9 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 #include "src/trace_redaction/scrub_ftrace_events.h"
-#include "perfetto/protozero/scattered_heap_buffer.h"
-#include "protos/perfetto/trace/ftrace/power.pbzero.h"
+#include "protos/perfetto/trace/ftrace/power.gen.h"
 #include "src/base/test/status_matchers.h"
 #include "test/gtest_and_gmock.h"
 
@@ -23,123 +23,42 @@
 #include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
 #include "protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.h"
 #include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
-#include "protos/perfetto/trace/ftrace/power.gen.h"
 #include "protos/perfetto/trace/ftrace/task.gen.h"
 #include "protos/perfetto/trace/ps/process_tree.gen.h"
 #include "protos/perfetto/trace/trace_packet.gen.h"
 
 namespace perfetto::trace_redaction {
 
-namespace {
-// task_rename should be in the allow-list.
-void AddTaskRename(protos::gen::FtraceEventBundle* bundle,
-                   int32_t pid,
-                   const std::string& old_comm,
-                   const std::string& new_comm) {
-  auto* e = bundle->add_event();
-  e->mutable_task_rename()->set_pid(pid);
-  e->mutable_task_rename()->set_oldcomm(old_comm);
-  e->mutable_task_rename()->set_newcomm(new_comm);
-}
-
-void AddClockSetRate(protos::gen::FtraceEventBundle* bundle,
-                     uint64_t cpu,
-                     const std::string& name,
-                     uint64_t state) {
-  auto* e = bundle->add_event();
-  e->mutable_clock_set_rate()->set_cpu_id(cpu);
-  e->mutable_clock_set_rate()->set_name(name);
-  e->mutable_clock_set_rate()->set_state(state);
-}
-
-}  // namespace
-
-class ScrubFtraceEventsSerializationTest : public testing::Test {
+// Tests which nested messages and fields are removed.
+class ScrubFtraceEventsTest : public testing::Test {
  public:
-  ScrubFtraceEventsSerializationTest() = default;
-  ~ScrubFtraceEventsSerializationTest() override = default;
+  ScrubFtraceEventsTest() = default;
+  ~ScrubFtraceEventsTest() override = default;
 
  protected:
-  void SetUp() override {
-    std::array bytes = {
-        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
-        0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
-    };
-
-    protozero::HeapBuffered<protozero::Message> msg;
-    msg->AppendFixed<uint32_t>(field_id_fixed_32, 7);
-    msg->AppendFixed<uint64_t>(field_id_fixed_64, 9);
-    msg->AppendVarInt(field_id_var_int, 723);
-    msg->AppendBytes(field_id_bytes, bytes.data(), bytes.size());
-
-    message_.assign(msg.SerializeAsString());
+  // task_rename should be in the allow-list.
+  static void AddTaskRename(protos::gen::FtraceEventBundle* bundle,
+                            int32_t pid,
+                            const std::string& old_comm,
+                            const std::string& new_comm) {
+    auto* e = bundle->add_event();
+    e->mutable_task_rename()->set_pid(pid);
+    e->mutable_task_rename()->set_oldcomm(old_comm);
+    e->mutable_task_rename()->set_newcomm(new_comm);
   }
 
-  std::string message_;
-
-  static constexpr uint32_t field_id_fixed_32 = 0;
-  static constexpr uint32_t field_id_fixed_64 = 1;
-  static constexpr uint32_t field_id_var_int = 2;
-  static constexpr uint32_t field_id_bytes = 3;
+  static void AddClockSetRate(protos::gen::FtraceEventBundle* bundle,
+                              uint64_t cpu,
+                              const std::string& name,
+                              uint64_t state) {
+    auto* e = bundle->add_event();
+    e->mutable_clock_set_rate()->set_cpu_id(cpu);
+    e->mutable_clock_set_rate()->set_name(name);
+    e->mutable_clock_set_rate()->set_state(state);
+  }
 };
 
-TEST_F(ScrubFtraceEventsSerializationTest, AppendFixedUint32) {
-  protozero::ProtoDecoder decoder(message_);
-
-  const auto& field = decoder.FindField(field_id_fixed_32);
-
-  std::string expected = "";
-  field.SerializeAndAppendTo(&expected);
-
-  protozero::HeapBuffered<protozero::Message> actual;
-  ScrubFtraceEvents::AppendField(field, actual.get());
-
-  ASSERT_EQ(actual.SerializeAsString(), expected);
-}
-
-TEST_F(ScrubFtraceEventsSerializationTest, AppendFixedUint64) {
-  protozero::ProtoDecoder decoder(message_);
-
-  const auto& field = decoder.FindField(field_id_fixed_64);
-
-  std::string expected = "";
-  field.SerializeAndAppendTo(&expected);
-
-  protozero::HeapBuffered<protozero::Message> actual;
-  ScrubFtraceEvents::AppendField(field, actual.get());
-
-  ASSERT_EQ(actual.SerializeAsString(), expected);
-}
-
-TEST_F(ScrubFtraceEventsSerializationTest, AppendBytes) {
-  protozero::ProtoDecoder decoder(message_);
-
-  const auto& field = decoder.FindField(field_id_bytes);
-
-  std::string expected = "";
-  field.SerializeAndAppendTo(&expected);
-
-  protozero::HeapBuffered<protozero::Message> actual;
-  ScrubFtraceEvents::AppendField(field, actual.get());
-
-  ASSERT_EQ(actual.SerializeAsString(), expected);
-}
-
-TEST_F(ScrubFtraceEventsSerializationTest, AppendVarInt) {
-  protozero::ProtoDecoder decoder(message_);
-
-  const auto& field = decoder.FindField(field_id_var_int);
-
-  std::string expected = "";
-  field.SerializeAndAppendTo(&expected);
-
-  protozero::HeapBuffered<protozero::Message> actual;
-  ScrubFtraceEvents::AppendField(field, actual.get());
-
-  ASSERT_EQ(actual.SerializeAsString(), expected);
-}
-
-TEST(ScrubFtraceEventsTest, ReturnErrorForNullPacket) {
+TEST_F(ScrubFtraceEventsTest, ReturnErrorForNullPacket) {
   // Have something in the allow-list to avoid that error.
   Context context;
   context.ftrace_packet_allow_list = {
@@ -149,7 +68,7 @@
   ASSERT_FALSE(scrub.Transform(context, nullptr).ok());
 }
 
-TEST(ScrubFtraceEventsTest, ReturnErrorForEmptyPacket) {
+TEST_F(ScrubFtraceEventsTest, ReturnErrorForEmptyPacket) {
   // Have something in the allow-list to avoid that error.
   Context context;
   context.ftrace_packet_allow_list = {
@@ -161,7 +80,7 @@
   ASSERT_FALSE(scrub.Transform(context, &packet_str).ok());
 }
 
-TEST(ScrubFtraceEventsTest, ReturnErrorForEmptyAllowList) {
+TEST_F(ScrubFtraceEventsTest, ReturnErrorForEmptyAllowList) {
   // The context will have no allow-list entries. ScrubFtraceEvents should fail.
   Context context;
 
@@ -172,7 +91,7 @@
   ASSERT_FALSE(scrub.Transform(context, &packet_str).ok());
 }
 
-TEST(ScrubFtraceEventsTest, IgnorePacketWithNoFtraceEvents) {
+TEST_F(ScrubFtraceEventsTest, IgnorePacketWithNoFtraceEvents) {
   protos::gen::TracePacket trace_packet;
   auto* tree = trace_packet.mutable_process_tree();
 
@@ -203,7 +122,7 @@
 
 // There are some values in a ftrace event that sits behind the ftrace bundle.
 // These values should be retained.
-TEST(ScrubFtraceEventsTest, KeepsFtraceBundleSiblingValues) {
+TEST_F(ScrubFtraceEventsTest, KeepsFtraceBundleSiblingValues) {
   protos::gen::TracePacket trace_packet;
   auto* ftrace_events = trace_packet.mutable_ftrace_events();
 
@@ -238,7 +157,7 @@
   ASSERT_TRUE(gen_events.event().front().has_task_rename());
 }
 
-TEST(ScrubFtraceEventsTest, KeepsAllowedEvents) {
+TEST_F(ScrubFtraceEventsTest, KeepsAllowedEvents) {
   Context context;
   context.ftrace_packet_allow_list = {
       protos::pbzero::FtraceEvent::kTaskRenameFieldNumber,
@@ -284,7 +203,7 @@
 }
 
 // Only the specific non-allowed events should be removed from the event list.
-TEST(ScrubFtraceEventsTest, OnlyDropsNotAllowedEvents) {
+TEST_F(ScrubFtraceEventsTest, OnlyDropsNotAllowedEvents) {
   // AddTaskRename >> Keep
   // AddClockSetRate >> Drop
   protos::gen::TracePacket original_packet;