This is an automated email from the ASF dual-hosted git repository.

jvanderzee pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 77b226d74f Add tests for writing documents to the cache (#11347)
77b226d74f is described below

commit 77b226d74fc28c79a3fab6b34986e01f6ed3555c
Author: JosiahWI <[email protected]>
AuthorDate: Thu May 30 07:59:23 2024 -0500

    Add tests for writing documents to the cache (#11347)
    
    * Add tests for writing documents to the cache
    
    This tests that the correct document data is written to cache by
    aggWrite. It also covers some fields of the Doc struct used for reading
    the data: the magic value, the length and header length, and the
    optional checksum.
    
    This commit also fixes the use of an uninitialized variable (header.phase)
    reported by valgrind by initializing it to the same default value used
    in the Stripe::_clear_init.
    
    * Test writing an evacuated document to the cache
    
    This tests VCs with f.evacuator set to make sure all main code paths in
    agg_copy are covered. It is very similar to the test for normal VCs, but
    sets the buffer up with a document instead of the raw content string.
    This commit also moves FakeVC and WaitingVC into a new file called
    test_doubles.h.
---
 src/iocore/cache/unit_tests/test_Stripe.cc | 255 +++++++++++++++++++----------
 src/iocore/cache/unit_tests/test_doubles.h | 125 ++++++++++++++
 2 files changed, 292 insertions(+), 88 deletions(-)

diff --git a/src/iocore/cache/unit_tests/test_Stripe.cc 
b/src/iocore/cache/unit_tests/test_Stripe.cc
index 5fe47b745b..ae5a05e9e1 100644
--- a/src/iocore/cache/unit_tests/test_Stripe.cc
+++ b/src/iocore/cache/unit_tests/test_Stripe.cc
@@ -22,12 +22,14 @@
  */
 
 #include "main.h"
+#include "test_doubles.h"
 
 #include "tscore/EventNotify.h"
 
 #include <array>
+#include <cstdint>
 #include <cstdio>
-#include <ostream>
+#include <cstring>
 
 // Required by main.h
 int  cache_vols           = 1;
@@ -79,79 +81,6 @@ std::array<AddWriterBranchTest, 32> 
add_writer_branch_test_cases = {
    }
 };
 
-class FakeVC : public CacheVC
-{
-public:
-  FakeVC()
-  {
-    this->buf = new_IOBufferData(iobuffer_size_to_index(1024, 
MAX_BUFFER_SIZE_INDEX), MEMALIGNED);
-    SET_HANDLER(&FakeVC::handle_call);
-  }
-
-  void
-  set_agg_len(int agg_len)
-  {
-    this->agg_len = agg_len;
-  }
-
-  void
-  set_header_len(int header_len)
-  {
-    this->header_len = header_len;
-  }
-
-  void
-  set_write_len(int write_len)
-  {
-    this->write_len = write_len;
-  }
-
-  void
-  set_readers(int readers)
-  {
-    this->f.readers = readers;
-  }
-
-  int
-  handle_call(int /* event ATS_UNUSED */, void * /* e ATS_UNUSED */)
-  {
-    return EVENT_CONT;
-  }
-};
-
-class WaitingVC final : public FakeVC
-{
-public:
-  WaitingVC(Stripe *stripe)
-  {
-    SET_HANDLER(&WaitingVC::handle_call);
-    this->stripe = stripe;
-    this->dir    = *stripe->dir;
-  }
-
-  void
-  wait_for_callback()
-  {
-    this->_notifier.lock();
-    while (!this->_got_callback) {
-      this->_notifier.wait();
-    }
-    this->_notifier.unlock();
-  }
-
-  int
-  handle_call(int /* event ATS_UNUSED */, void * /* e ATS_UNUSED */)
-  {
-    this->_got_callback = true;
-    this->_notifier.signal();
-    return EVENT_CONT;
-  }
-
-private:
-  EventNotify _notifier;
-  bool        _got_callback{false};
-};
-
 /* Catch test helper to provide a Stripe with a valid file descriptor.
  *
  * The file will be deleted automatically when the application ends normally.
@@ -175,12 +104,14 @@ attach_tmpfile_to_stripe(Stripe &stripe)
 
 // We can't return a stripe from this function because the copy
 // and move constructors are deleted.
-static void
+static std::FILE *
 init_stripe_for_writing(Stripe &stripe, StripteHeaderFooter &header, CacheVol 
&cache_vol)
 {
   stripe.cache_vol                                = &cache_vol;
   cache_rsb.write_backlog_failure                 = 
Metrics::Counter::createPtr("unit_test.write.backlog.failure");
   stripe.cache_vol->vol_rsb.write_backlog_failure = 
Metrics::Counter::createPtr("unit_test.write.backlog.failure");
+  cache_rsb.gc_frags_evacuated                    = 
Metrics::Counter::createPtr("unit_test.gc.frags.evacuated");
+  stripe.cache_vol->vol_rsb.gc_frags_evacuated    = 
Metrics::Counter::createPtr("unit_test.gc.frags.evacuated");
 
   // A number of things must be initialized in a certain way for Stripe
   // not to segfault, hit an assertion, or exhibit zero-division.
@@ -199,8 +130,9 @@ init_stripe_for_writing(Stripe &stripe, StripteHeaderFooter 
&header, CacheVol &c
 
   header.write_pos = 50000;
   header.agg_pos   = 1;
+  header.phase     = 0;
   stripe.header    = &header;
-  attach_tmpfile_to_stripe(stripe);
+  return attach_tmpfile_to_stripe(stripe);
 }
 
 TEST_CASE("The behavior of Stripe::add_writer.")
@@ -260,14 +192,16 @@ TEST_CASE("The behavior of Stripe::add_writer.")
 // This test case demonstrates how to set up a Stripe and make
 // a call to aggWrite without causing memory errors. It uses a
 // tmpfile for the Stripe to write to.
-TEST_CASE("aggWrite behavior")
+TEST_CASE("aggWrite behavior with f.evacuator unset")
 {
   Stripe              stripe;
   StripteHeaderFooter header;
   CacheVol            cache_vol;
-  init_stripe_for_writing(stripe, header, cache_vol);
-  WaitingVC vc{&stripe};
-  vc.set_write_len(1);
+  auto               *file{init_stripe_for_writing(stripe, header, cache_vol)};
+  WaitingVC           vc{&stripe};
+  char const         *source = "yay";
+  vc.set_test_data(source, 4);
+  vc.set_write_len(4);
   vc.set_agg_len(stripe.round_to_approx_size(vc.write_len + vc.header_len + 
vc.frag_len));
   stripe.add_writer(&vc);
 
@@ -284,26 +218,171 @@ TEST_CASE("aggWrite behavior")
     CHECK(0 == header.agg_pos);
   }
 
-  SECTION("Given the aggregation buffer is partially full and sync is set, "
-          "when we schedule aggWrite, "
-          "then some bytes should be written to disk.")
+  SECTION("Given the aggregation buffer is partially full, sync is set, "
+          "and checksums are enabled, "
+          "when we schedule aggWrite with a VC buffer containing 'yay', ")
+  {
+    cache_config_enable_checksum = true;
+    vc.f.sync                    = 1;
+    vc.f.use_first_key           = 1;
+    vc.write_serial              = 1;
+    header.write_serial          = 10;
+    int document_offset          = header.write_pos;
+    {
+      SCOPED_MUTEX_LOCK(lock, stripe.mutex, this_ethread());
+      stripe.aggWrite(EVENT_NONE, 0);
+    }
+    vc.wait_for_callback();
+
+    SECTION("then some bytes should be written to disk")
+    {
+      CHECK(0 < header.agg_pos);
+    }
+
+    Doc doc;
+
+    {
+      // Other threads are still alive and may interact with the
+      // cache periodically, so we should always grab the lock
+      // whenever we interact with the underlying file.
+      SCOPED_MUTEX_LOCK(lock, stripe.mutex, this_ethread());
+      fseek(file, document_offset, SEEK_SET);
+      fread(&doc, sizeof(Doc), 1, file);
+    }
+
+    // An incorrect magic value would indicate that the document is corrupt.
+    REQUIRE(DOC_MAGIC == doc.magic);
+
+    SECTION("then the document should be single fragment.")
+    {
+      CHECK(doc.single_fragment());
+    }
+
+    SECTION("then the document header length should be 0.")
+    {
+      CHECK(0 == doc.hlen);
+    }
+
+    SECTION("then the document length should be sizeof(Doc) + 4.")
+    {
+      CHECK(sizeof(Doc) + 4 == doc.len);
+    }
+
+    SECTION("then the document checksum should be correct.")
+    {
+      std::uint32_t expected = 'y' + 'a' + 'y' + '\0';
+      CHECK(expected == doc.checksum);
+    }
+
+    SECTION("then the document data should contain 'yay'.")
+    {
+      char *data = new char[doc.data_len()];
+      {
+        SCOPED_MUTEX_LOCK(lock, stripe.mutex, this_ethread());
+        fseek(file, document_offset + doc.prefix_len(), SEEK_SET);
+        fread(data, doc.data_len(), 1, file);
+      }
+      INFO("buffer content is \"" << data << "\"");
+      CHECK(0 == strncmp("yay", data, 3));
+      delete[] data;
+    }
+
+    cache_config_enable_checksum = false;
+  }
+
+  ats_free(stripe.raw_dir);
+  ats_free(stripe.evacuate);
+}
+
+// When f.evacuator is set, vc.buf must contain a Doc object including headers
+// and data.
+// We don't use a CacheEvacuateDocVC because the behavior under test depends
+// only on the presence of the f.evacuator flag.
+TEST_CASE("aggWrite behavior with f.evacuator set")
+{
+  Stripe              stripe;
+  StripteHeaderFooter header;
+  CacheVol            cache_vol;
+  auto               *file{init_stripe_for_writing(stripe, header, cache_vol)};
+  WaitingVC           vc{&stripe};
+  char               *source = new char[sizeof(Doc) + 4]{};
+  const char         *yay    = "yay";
+  Doc                 doc{};
+  doc.magic     = DOC_MAGIC;
+  doc.len       = sizeof(Doc) + 4;
+  doc.total_len = 4;
+  doc.hlen      = 0;
+  std::memcpy(source, &doc, sizeof(doc));
+  std::memcpy(source + sizeof(Doc), yay, 4);
+  vc.set_test_data(source, sizeof(Doc) + 4);
+  vc.set_write_len(stripe.round_to_approx_size(doc.len));
+  vc.set_agg_len(stripe.round_to_approx_size(vc.write_len + vc.header_len + 
vc.frag_len));
+  vc.mark_as_evacuator();
+  stripe.add_writer(&vc);
+
+  SECTION("Given the aggregation buffer is partially full, sync is set, "
+          "when we schedule aggWrite with a VC buffer containing 'yay', ")
   {
     vc.f.sync           = 1;
     vc.f.use_first_key  = 1;
     vc.write_serial     = 1;
     header.write_serial = 10;
+    int document_offset = header.write_pos;
     {
       SCOPED_MUTEX_LOCK(lock, stripe.mutex, this_ethread());
       stripe.aggWrite(EVENT_NONE, 0);
     }
     vc.wait_for_callback();
-    // We don't check here what bytes were written. In fact according
-    // to valgrind it's writing uninitialized parts of the aggregation
-    // buffer, but that's OK because in this SECTION we only care that
-    // something was written successfully without anything blowing up.
-    CHECK(0 < header.agg_pos);
+
+    SECTION("then some bytes should be written to disk")
+    {
+      CHECK(0 < header.agg_pos);
+    }
+
+    Doc doc;
+
+    {
+      // Other threads are still alive and may interact with the
+      // cache periodically, so we should always grab the lock
+      // whenever we interact with the underlying file.
+      SCOPED_MUTEX_LOCK(lock, stripe.mutex, this_ethread());
+      fseek(file, document_offset, SEEK_SET);
+      fread(&doc, sizeof(Doc), 1, file);
+    }
+
+    // An incorrect magic value would indicate that the document is corrupt.
+    REQUIRE(DOC_MAGIC == doc.magic);
+
+    SECTION("then the document should be single fragment.")
+    {
+      CHECK(doc.single_fragment());
+    }
+
+    SECTION("then the document header length should be 0.")
+    {
+      CHECK(0 == doc.hlen);
+    }
+
+    SECTION("then the document length should be sizeof(Doc) + 4.")
+    {
+      CHECK(sizeof(Doc) + 4 == doc.len);
+    }
+
+    SECTION("then the document data should contain 'yay'.")
+    {
+      char *data = new char[doc.data_len()];
+      {
+        SCOPED_MUTEX_LOCK(lock, stripe.mutex, this_ethread());
+        fseek(file, document_offset + doc.prefix_len(), SEEK_SET);
+        fread(data, doc.data_len(), 1, file);
+      }
+      INFO("buffer content is \"" << data << "\"");
+      CHECK(0 == strncmp("yay", data, 3));
+      delete[] data;
+    }
   }
 
+  delete[] source;
   ats_free(stripe.raw_dir);
   ats_free(stripe.evacuate);
 }
diff --git a/src/iocore/cache/unit_tests/test_doubles.h 
b/src/iocore/cache/unit_tests/test_doubles.h
new file mode 100644
index 0000000000..3ab4e1f8cd
--- /dev/null
+++ b/src/iocore/cache/unit_tests/test_doubles.h
@@ -0,0 +1,125 @@
+/** @file
+
+  A brief file description
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you 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.
+ */
+
+#pragma once
+
+#include "main.h"
+
+#include "tscore/EventNotify.h"
+
+#include <cstdint>
+#include <cstring>
+#include <stdexcept>
+
+class FakeVC : public CacheVC
+{
+public:
+  FakeVC()
+  {
+    this->buf    = new_IOBufferData(iobuffer_size_to_index(1024, 
MAX_BUFFER_SIZE_INDEX), MEMALIGNED);
+    this->blocks = new_IOBufferBlock();
+    this->blocks->set(this->buf.get());
+    SET_HANDLER(&FakeVC::handle_call);
+  }
+
+  void
+  set_test_data(char const *source, int len)
+  {
+    this->blocks->reset();
+    if (len > 1024) {
+      throw std::runtime_error{"data length exceeds internal buffer"};
+    }
+    std::memcpy(this->buf->data(), source, len);
+    this->blocks->fill(len);
+  }
+
+  void
+  set_agg_len(int agg_len)
+  {
+    this->agg_len = agg_len;
+  }
+
+  void
+  set_header_len(int header_len)
+  {
+    this->header_len = header_len;
+  }
+
+  void
+  set_write_len(int write_len)
+  {
+    this->write_len = write_len;
+    this->total_len = static_cast<std::uint64_t>(write_len);
+  }
+
+  void
+  set_readers(int readers)
+  {
+    this->f.readers = readers;
+  }
+
+  void
+  mark_as_evacuator()
+  {
+    this->f.evacuator = true;
+  }
+
+  int
+  handle_call(int /* event ATS_UNUSED */, void * /* e ATS_UNUSED */)
+  {
+    return EVENT_CONT;
+  }
+};
+
+class WaitingVC final : public FakeVC
+{
+public:
+  WaitingVC(Stripe *stripe)
+  {
+    SET_HANDLER(&WaitingVC::handle_call);
+    this->stripe = stripe;
+    this->dir    = *stripe->dir;
+  }
+
+  void
+  wait_for_callback()
+  {
+    this->_notifier.lock();
+    while (!this->_got_callback) {
+      this->_notifier.wait();
+    }
+    this->_notifier.unlock();
+  }
+
+  int
+  handle_call(int /* event ATS_UNUSED */, void * /* e ATS_UNUSED */)
+  {
+    this->_got_callback = true;
+    this->_notifier.signal();
+    return EVENT_CONT;
+  }
+
+private:
+  EventNotify _notifier;
+  bool        _got_callback{false};
+};

Reply via email to