Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  TestLockstoreService.cpp

  Sprache: C
 

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include <atomic>
#include <set>

#include "gtest/gtest.h"
#include "mozilla/SpinEventLoopUntil.h"
#include "mozilla/gtest/MozAssertions.h"
#include "LockstoreService.h"
#include "nsString.h"
#include "nsTArray.h"
#include "nsThreadUtils.h"

using namespace mozilla;
using namespace mozilla::security::lockstore;

namespace {

nsCString UniqueCollection(const char* aPrefix) {
  // Service-level state is a process-wide singleton bound to the test
  // profile, so we suffix every collection with a counter to keep tests
  // independent within the same binary run.
  static uint32_t sCounter = 0;
  nsCString out;
  out.AppendASCII(aPrefix);
  out.AppendLiteral("-");
  out.AppendInt(++sCounter);
  return out;
}

// Runs `aFn` on a background task and spins the main loop until it
// completes. Used to drive the off-main-thread sync `Do*` methods from
// gtest, which runs on the main thread.
template <typename Fn>
void RunOnBackground(Fn&& aFn) {
  // `done` is touched only on the main thread (set by the completion
  // runnable, read by the predicate), so it needs no synchronization.
  bool done = false;
  MOZ_ALWAYS_SUCCEEDS(NS_DispatchBackgroundTask(NS_NewRunnableFunction(
      "TestLockstoreService::RunOnBackground",
      [&done, fn = std::forward<Fn>(aFn)]() mutable {
        fn();
        // The predicate is satisfied from this background task, so post
        // completion back to the main thread. This wakes the blocked
        // NS_ProcessNextEvent inside SpinEventLoopUntil; otherwise the spin
        // only re-checks `done` when unrelated event-loop traffic happens to
        // wake the main thread, and those incidental wakeups grow sparse as
        // the process quiesces -- stretching each wait until it trips the
        // gtest no-output watchdog.
        NS_DispatchToMainThread(NS_NewRunnableFunction(
            "TestLockstoreService::RunOnBackground::Done",
            [&done] { done = true; }));
      })));
  MOZ_ALWAYS_TRUE(
      SpinEventLoopUntil("RunOnBackground"_ns, [&done]() { return done; }));
}

nsTArray<uint8_t> Bytes(const char* aLiteral) {
  nsTArray<uint8_t> out;
  out.AppendElements(reinterpret_cast<const uint8_t*>(aLiteral),
                     strlen(aLiteral));
  return out;
}

}  // namespace

class LockstoreServiceTest : public ::testing::Test {
 protected:
  void SetUp() override {
    mService = LockstoreService::GetSingleton();
    ASSERT_TRUE(mService)
    << "LockstoreService singleton must be obtainable";

    // Mint two fresh LocalKey kek_refs for this test. Every test gets
    // independent kek_refs so collection lifetimes don't interfere
    // across tests sharing the process-wide service singleton, and
    // tests that need "a different KEK" (wrong-kek decrypt, addKek)
    // have a second one to reach for.
    RunOnBackground([&]() {
      auto k1 = mService->DoCreateKek("local"_ns, /*identifier=*/""_ns, ""_ns,
                                      /*cacheTimeoutMs=*/0);
      ASSERT_TRUE(k1.isOk())
      << "DoCreateKek(local) must succeed";
      mLocalKek = k1.unwrap();
      ASSERT_FALSE(mLocalKek.IsEmpty())
      << "DoCreateKek(local) must mint a non-empty kek_ref";

      auto k2 = mService->DoCreateKek("local"_ns, /*identifier=*/""_ns, ""_ns,
                                      /*cacheTimeoutMs=*/0);
      ASSERT_TRUE(k2.isOk())
      << "DoCreateKek(local) must succeed";
      mOtherKek = k2.unwrap();
      ASSERT_FALSE(mOtherKek.IsEmpty())
      << "DoCreateKek(local) must mint a non-empty kek_ref";
    });
  }

  void TearDown() override {
    // The service singleton persists across the gtest binary, so any KEKs
    // left behind accumulate in the store. Drop the per-test KEKs to keep
    // each test's footprint bounded. Best-effort: a test that already
    // deleted its KEKs (or never finished SetUp) sees the second call no-op.
    if (mLocalKek.IsEmpty() && mOtherKek.IsEmpty()) {
      return;
    }
    RunOnBackground([&]() {
      if (!mLocalKek.IsEmpty()) {
        mService->DoDeleteKek(mLocalKek);
      }
      if (!mOtherKek.IsEmpty()) {
        mService->DoDeleteKek(mOtherKek);
      }
    });
  }

  // Fresh-per-test LocalKey kek_refs.
  nsCString mLocalKek;
  nsCString mOtherKek;
  RefPtr<LockstoreService> mService;
};

// ---------------------------------------------------------------------------
// Singleton / lifecycle
// ---------------------------------------------------------------------------

TEST_F(LockstoreServiceTest, SingletonIdentity) {
  RefPtr<LockstoreService> a = LockstoreService::GetSingleton();
  RefPtr<LockstoreService> b = LockstoreService::GetSingleton();
  EXPECT_EQ(a.get(), b.get()) << "GetSingleton must return the same instance";
}

// ---------------------------------------------------------------------------
// createDek / listCollections / deleteDek
// ---------------------------------------------------------------------------

TEST_F(LockstoreServiceTest, CreateAndDeleteDek) {
  nsCString coll = UniqueCollection("create-delete");

  RunOnBackground([&]() {
    EXPECT_NS_SUCCEEDED(mService->DoCreateDek(
        coll, mLocalKek, /*extractable=*/false, /*keySize=*/32));

    auto collectionsResult = mService->DoListDeks();
    ASSERT_TRUE(collectionsResult.isOk());
    auto collections = collectionsResult.unwrap();
    bool found = false;
    for (const auto& c : collections) {
      if (c == coll) {
        found = true;
        break;
      }
    }
    EXPECT_TRUE(found) << "Created collection should appear in listCollections";

    EXPECT_NS_SUCCEEDED(mService->DoDeleteDek(coll));

    // Second call rejects since the DEK is gone.
    EXPECT_EQ(mService->DoDeleteDek(coll), NS_ERROR_NOT_AVAILABLE);
  });
}

TEST_F(LockstoreServiceTest, CreateDek_DuplicateRejects) {
  nsCString coll = UniqueCollection("dup");
  RunOnBackground([&]() {
    EXPECT_NS_SUCCEEDED(mService->DoCreateDek(
        coll, mLocalKek, /*extractable=*/false, /*keySize=*/32));

    EXPECT_EQ(mService->DoCreateDek(coll, mLocalKek, /*extractable=*/false,
                                    /*keySize=*/32),
              NS_ERROR_FAILURE)
        << "createDek on an existing collection must reject";

    mService->DoDeleteDek(coll);
  });
}

TEST_F(LockstoreServiceTest, CreateDek_RejectsEmptyCollection) {
  RunOnBackground([&]() {
    EXPECT_EQ(mService->DoCreateDek(""_ns, mLocalKek, /*extractable=*/false,
                                    /*keySize=*/32),
              NS_ERROR_INVALID_ARG);
  });
}

TEST_F(LockstoreServiceTest, CreateDek_RejectsEmptyKekRef) {
  nsCString coll = UniqueCollection("empty-kek");
  RunOnBackground([&]() {
    EXPECT_EQ(mService->DoCreateDek(coll, ""_ns, /*extractable=*/false,
                                    /*keySize=*/32),
              NS_ERROR_INVALID_ARG);
  });
}

TEST_F(LockstoreServiceTest, DeleteDek_RejectsEmptyArg) {
  RunOnBackground(
      [&]() { EXPECT_EQ(mService->DoDeleteDek(""_ns), NS_ERROR_INVALID_ARG); });
}

TEST_F(LockstoreServiceTest, ListDeks_ContainsCreated) {
  // Create three uniquely-named collections; listCollections must
  // include all three. The list may also include collections from
  // unrelated tests, so we only assert subset, not equality.
  nsCString a = UniqueCollection("list-a");
  nsCString b = UniqueCollection("list-b");
  nsCString c = UniqueCollection("list-c");
  RunOnBackground([&]() {
    EXPECT_NS_SUCCEEDED(mService->DoCreateDek(
        a, mLocalKek, /*extractable=*/false, /*keySize=*/32));
    EXPECT_NS_SUCCEEDED(mService->DoCreateDek(
        b, mLocalKek, /*extractable=*/false, /*keySize=*/32));
    EXPECT_NS_SUCCEEDED(mService->DoCreateDek(
        c, mLocalKek, /*extractable=*/false, /*keySize=*/32));

    auto listResult = mService->DoListDeks();
    ASSERT_TRUE(listResult.isOk());
    auto list = listResult.unwrap();
    std::set<nsCString> names;
    for (const auto& n : list) {
      names.insert(n);
    }
    EXPECT_TRUE(names.count(a)) << "listCollections missing " << a.get();
    EXPECT_TRUE(names.count(b)) << "listCollections missing " << b.get();
    EXPECT_TRUE(names.count(c)) << "listCollections missing " << c.get();

    mService->DoDeleteDek(a);
    mService->DoDeleteDek(b);
    mService->DoDeleteDek(c);
  });
}

// ---------------------------------------------------------------------------
// listKeks
// ---------------------------------------------------------------------------

TEST_F(LockstoreServiceTest, ListKeks_ReflectsCreateDek) {
  nsCString coll = UniqueCollection("keks-create");
  RunOnBackground([&]() {
    EXPECT_NS_SUCCEEDED(mService->DoCreateDek(
        coll, mLocalKek, /*extractable=*/false, /*keySize=*/32));

    auto refsResult = mService->DoListKeks(coll);
    ASSERT_TRUE(refsResult.isOk());
    auto refs = refsResult.unwrap();
    ASSERT_EQ(refs.Length(), 1u);
    EXPECT_EQ(refs[0], mLocalKek);

    mService->DoDeleteDek(coll);
  });
}

TEST_F(LockstoreServiceTest, ListKeks_RejectsNoDek) {
  nsCString coll = UniqueCollection("keks-missing");
  RunOnBackground([&]() {
    auto refsResult = mService->DoListKeks(coll);
    EXPECT_TRUE(refsResult.isErr());
    EXPECT_EQ(refsResult.unwrapErr(), NS_ERROR_NOT_AVAILABLE);
  });
}

TEST_F(LockstoreServiceTest, ListKeks_RejectsEmptyCollection) {
  RunOnBackground([&]() {
    auto refsResult = mService->DoListKeks(""_ns);
    EXPECT_TRUE(refsResult.isErr());
    // Empty / unknown collection surfaces as NS_ERROR_NOT_AVAILABLE: the
    // keystore layer rejects the lookup with `NotFound`, which the FFI
    // maps via `error_to_nsresult`. The FFI no longer pre-rejects empty
    // strings at the boundary.
    EXPECT_EQ(refsResult.unwrapErr(), NS_ERROR_NOT_AVAILABLE);
  });
}

// ---------------------------------------------------------------------------
// encrypt / decrypt
// ---------------------------------------------------------------------------

TEST_F(LockstoreServiceTest, EncryptDecryptRoundtrip) {
  nsCString coll = UniqueCollection("roundtrip");
  RunOnBackground([&]() {
    EXPECT_NS_SUCCEEDED(mService->DoCreateDek(
        coll, mLocalKek, /*extractable=*/false, /*keySize=*/32));

    auto ctResult = mService->DoEncrypt(coll, mLocalKek, Bytes("hello world"));
    ASSERT_TRUE(ctResult.isOk());
    auto ciphertext = ctResult.unwrap();
    EXPECT_GT(ciphertext.Length(), 0u);

    auto ptResult = mService->DoDecrypt(coll, mLocalKek, ciphertext);
    ASSERT_TRUE(ptResult.isOk());
    auto plaintext = ptResult.unwrap();
    nsCString joined;
    for (uint8_t b : plaintext) {
      joined.Append(static_cast<char>(b));
    }
    EXPECT_STREQ(joined.get(), "hello world");

    mService->DoDeleteDek(coll);
  });
}

TEST_F(LockstoreServiceTest, Encrypt_YieldsUniqueCiphertexts) {
  nsCString coll = UniqueCollection("nonce");
  RunOnBackground([&]() {
    EXPECT_NS_SUCCEEDED(mService->DoCreateDek(
        coll, mLocalKek, /*extractable=*/false, /*keySize=*/32));

    auto a = mService->DoEncrypt(coll, mLocalKek, Bytes("same"));
    auto b = mService->DoEncrypt(coll, mLocalKek, Bytes("same"));
    ASSERT_TRUE(a.isOk());
    ASSERT_TRUE(b.isOk());
    auto ctA = a.unwrap();
    auto ctB = b.unwrap();
    EXPECT_NE(ctA, ctB) << "Repeated encrypts of the same plaintext must "
                           "yield distinct ciphertexts (random nonce)";

    mService->DoDeleteDek(coll);
  });
}

TEST_F(LockstoreServiceTest, Decrypt_CorruptedCiphertextRejects) {
  nsCString coll = UniqueCollection("corrupt");
  RunOnBackground([&]() {
    EXPECT_NS_SUCCEEDED(mService->DoCreateDek(
        coll, mLocalKek, /*extractable=*/false, /*keySize=*/32));

    auto ctResult = mService->DoEncrypt(coll, mLocalKek, Bytes("payload"));
    ASSERT_TRUE(ctResult.isOk());
    auto ct = ctResult.unwrap();
    // Flip a byte in the middle to corrupt the AEAD tag.
    if (ct.Length() > 0) {
      ct[ct.Length() / 2] ^= 0xff;
    }

    auto ptResult = mService->DoDecrypt(coll, mLocalKek, ct);
    EXPECT_TRUE(ptResult.isErr());

    mService->DoDeleteDek(coll);
  });
}

TEST_F(LockstoreServiceTest, Decrypt_TruncatedCiphertextRejects) {
  nsCString coll = UniqueCollection("trunc");
  RunOnBackground([&]() {
    EXPECT_NS_SUCCEEDED(mService->DoCreateDek(
        coll, mLocalKek, /*extractable=*/false, /*keySize=*/32));

    auto ctResult = mService->DoEncrypt(coll, mLocalKek, Bytes("payload"));
    ASSERT_TRUE(ctResult.isOk());
    auto ct = ctResult.unwrap();
    if (ct.Length() > 8) {
      ct.SetLength(ct.Length() / 2);
    }

    auto ptResult = mService->DoDecrypt(coll, mLocalKek, ct);
    EXPECT_TRUE(ptResult.isErr());

    mService->DoDeleteDek(coll);
  });
}

TEST_F(LockstoreServiceTest, Decrypt_WrongKekRejects) {
  nsCString coll = UniqueCollection("wrong-kek");
  RunOnBackground([&]() {
    EXPECT_NS_SUCCEEDED(mService->DoCreateDek(
        coll, mLocalKek, /*extractable=*/false, /*keySize=*/32));

    auto ctResult = mService->DoEncrypt(coll, mLocalKek, Bytes("payload"));
    ASSERT_TRUE(ctResult.isOk());
    auto ct = ctResult.unwrap();

    auto ptResult = mService->DoDecrypt(coll, mOtherKek, ct);
    EXPECT_TRUE(ptResult.isErr());

    mService->DoDeleteDek(coll);
  });
}

TEST_F(LockstoreServiceTest, Encrypt_NoDekRejects) {
  nsCString coll = UniqueCollection("no-dek");
  RunOnBackground([&]() {
    auto ctResult = mService->DoEncrypt(coll, mLocalKek, Bytes("payload"));
    EXPECT_TRUE(ctResult.isErr());
  });
}

TEST_F(LockstoreServiceTest, Encrypt_RejectsEmptyArgs) {
  nsCString coll = UniqueCollection("empty-enc");
  RunOnBackground([&]() {
    EXPECT_NS_SUCCEEDED(mService->DoCreateDek(
        coll, mLocalKek, /*extractable=*/false, /*keySize=*/32));

    EXPECT_TRUE(mService->DoEncrypt(""_ns, mLocalKek, Bytes("x")).isErr());
    EXPECT_TRUE(mService->DoEncrypt(coll, ""_ns, Bytes("x")).isErr());

    mService->DoDeleteDek(coll);
  });
}

TEST_F(LockstoreServiceTest, Decrypt_NoDekRejects) {
  nsCString coll = UniqueCollection("no-dek-dec");
  RunOnBackground([&]() {
    nsTArray<uint8_t> bogus;
    bogus.AppendElements(static_cast<const uint8_t*>(
                             reinterpret_cast<const uint8_t*>("\0\0\0\0\0\0")),
                         6);
    auto ptResult = mService->DoDecrypt(coll, mLocalKek, bogus);
    EXPECT_TRUE(ptResult.isErr());
  });
}

TEST_F(LockstoreServiceTest, Decrypt_RejectsEmptyArgs) {
  nsCString coll = UniqueCollection("empty-dec");
  RunOnBackground([&]() {
    EXPECT_NS_SUCCEEDED(mService->DoCreateDek(
        coll, mLocalKek, /*extractable=*/false, /*keySize=*/32));

    auto ctResult = mService->DoEncrypt(coll, mLocalKek, Bytes("x"));
    ASSERT_TRUE(ctResult.isOk());
    auto ct = ctResult.unwrap();

    EXPECT_TRUE(mService->DoDecrypt(""_ns, mLocalKek, ct).isErr());
    EXPECT_TRUE(mService->DoDecrypt(coll, ""_ns, ct).isErr());

    mService->DoDeleteDek(coll);
  });
}

// ---------------------------------------------------------------------------
// addKek / removeKek
// ---------------------------------------------------------------------------

TEST_F(LockstoreServiceTest, AddKek_RejectsMissingCollection) {
  nsCString coll = UniqueCollection("addkek-missing");
  RunOnBackground([&]() {
    EXPECT_EQ(mService->DoAddKek(coll, mLocalKek, mOtherKek),
              NS_ERROR_NOT_AVAILABLE);
  });
}

TEST_F(LockstoreServiceTest, AddKek_RejectsEmptyArgs) {
  nsCString coll = UniqueCollection("addkek-empty");
  RunOnBackground([&]() {
    EXPECT_NS_SUCCEEDED(mService->DoCreateDek(
        coll, mLocalKek, /*extractable=*/false, /*keySize=*/32));

    EXPECT_EQ(mService->DoAddKek(""_ns, mLocalKek, mOtherKek),
              NS_ERROR_INVALID_ARG);
    EXPECT_EQ(mService->DoAddKek(coll, ""_ns, mOtherKek), NS_ERROR_INVALID_ARG);
    EXPECT_EQ(mService->DoAddKek(coll, mLocalKek, ""_ns), NS_ERROR_INVALID_ARG);

    mService->DoDeleteDek(coll);
  });
}

TEST_F(LockstoreServiceTest, RemoveKek_RejectsEmptyArgs) {
  nsCString coll = UniqueCollection("rmkek-empty");
  RunOnBackground([&]() {
    EXPECT_NS_SUCCEEDED(mService->DoCreateDek(
        coll, mLocalKek, /*extractable=*/false, /*keySize=*/32));

    EXPECT_EQ(mService->DoRemoveKek(""_ns, mLocalKek), NS_ERROR_INVALID_ARG);
    EXPECT_EQ(mService->DoRemoveKek(coll, ""_ns), NS_ERROR_INVALID_ARG);

    mService->DoDeleteDek(coll);
  });
}

TEST_F(LockstoreServiceTest, RemoveKek_LastWrappingRejects) {
  nsCString coll = UniqueCollection("rmkek-last");
  RunOnBackground([&]() {
    EXPECT_NS_SUCCEEDED(mService->DoCreateDek(
        coll, mLocalKek, /*extractable=*/false, /*keySize=*/32));

    // Removing the last KEK wrapping must be rejected — otherwise the
    // DEK would become unrecoverable.
    EXPECT_EQ(mService->DoRemoveKek(coll, mLocalKek), NS_ERROR_FAILURE);

    mService->DoDeleteDek(coll);
  });
}

// ---------------------------------------------------------------------------
// Threading: concurrent dispatches all complete with `mMutex`
// serialising under the hood.
// ---------------------------------------------------------------------------

TEST_F(LockstoreServiceTest, ConcurrentEncryptsAllResolveUnique) {
  // Dispatch N encrypts in parallel on independent background tasks.
  // The service's `mMutex` guarantees they execute one at a time;
  // each must produce a distinct ciphertext (random nonce).
  nsCString coll = UniqueCollection("concurrent");
  RunOnBackground([&]() {
    EXPECT_NS_SUCCEEDED(mService->DoCreateDek(
        coll, mLocalKek, /*extractable=*/false, /*keySize=*/32));
  });

  constexpr size_t N = 8;
  nsTArray<nsTArray<uint8_t>> results;
  results.SetLength(N);
  std::atomic<size_t> doneCount{0};

  for (size_t i = 0; i < N; ++i) {
    MOZ_ALWAYS_SUCCEEDS(NS_DispatchBackgroundTask(NS_NewRunnableFunction(
        "ConcurrentEncryptsAllResolveUnique::worker",
        [this, &coll, &results, &doneCount, i]() {
          auto r = mService->DoEncrypt(coll, mLocalKek, Bytes("same-input"));
          if (r.isOk()) {
            results[i] = r.unwrap();
          }
          // Record completion on the main thread so the spin loop is woken
          // promptly (see RunOnBackground).
          NS_DispatchToMainThread(
              NS_NewRunnableFunction("ConcurrentEncryptsAllResolveUnique::done",
                                     [&doneCount]() { ++doneCount; }));
        })));
  }
  MOZ_ALWAYS_TRUE(
      SpinEventLoopUntil("ConcurrentEncryptsAllResolveUnique"_ns,
                         [&doneCount]() { return doneCount.load() == N; }));

  std::set<nsCString> seen;
  for (const auto& ct : results) {
    EXPECT_GT(ct.Length(), 0u);
    nsCString joined;
    for (uint8_t b : ct) {
      joined.AppendInt(static_cast<uint32_t>(b));
      joined.AppendLiteral(",");
    }
    seen.insert(std::move(joined));
  }
  EXPECT_EQ(seen.size(), N)
      << "Every concurrent encrypt must produce a unique ciphertext";

  RunOnBackground([&]() { mService->DoDeleteDek(coll); });
}

TEST_F(LockstoreServiceTest, ConcurrentMixedOpsAllComplete) {
  // Mix createDek / encrypt-decrypt / deleteDek across multiple
  // collections concurrently. All ops must complete without deadlock,
  // and listCollections at the end must reflect the post-cleanup state.
  constexpr size_t N = 4;
  nsTArray<nsCString> colls;
  for (size_t i = 0; i < N; ++i) {
    colls.AppendElement(UniqueCollection("mix"));
  }

  // Concurrent creates.
  std::atomic<size_t> createDone{0};
  for (size_t i = 0; i < N; ++i) {
    const nsCString& c = colls[i];
    MOZ_ALWAYS_SUCCEEDS(NS_DispatchBackgroundTask(NS_NewRunnableFunction(
        "ConcurrentMixedOps::create", [this, &c, &createDone]() {
          EXPECT_NS_SUCCEEDED(mService->DoCreateDek(
              c, mLocalKek, /*extractable=*/false, /*keySize=*/32));
          NS_DispatchToMainThread(
              NS_NewRunnableFunction("ConcurrentMixedOps::create-done",
                                     [&createDone]() { ++createDone; }));
        })));
  }
  MOZ_ALWAYS_TRUE(
      SpinEventLoopUntil("ConcurrentMixedOps::create-wait"_ns,
                         [&createDone]() { return createDone.load() == N; }));

  // Concurrent encrypt + decrypt round-trips per collection.
  std::atomic<size_t> roundtripDone{0};
  for (size_t i = 0; i < N; ++i) {
    const nsCString& c = colls[i];
    MOZ_ALWAYS_SUCCEEDS(NS_DispatchBackgroundTask(NS_NewRunnableFunction(
        "ConcurrentMixedOps::roundtrip", [this, &c, &roundtripDone]() {
          auto ctResult = mService->DoEncrypt(c, mLocalKek, Bytes("payload"));
          EXPECT_TRUE(ctResult.isOk());
          if (ctResult.isOk()) {
            auto ct = ctResult.unwrap();
            EXPECT_GT(ct.Length(), 0u);
            auto ptResult = mService->DoDecrypt(c, mLocalKek, ct);
            EXPECT_TRUE(ptResult.isOk());
            if (ptResult.isOk()) {
              EXPECT_EQ(ptResult.unwrap().Length(), strlen("payload"));
            }
          }
          NS_DispatchToMainThread(
              NS_NewRunnableFunction("ConcurrentMixedOps::roundtrip-done",
                                     [&roundtripDone]() { ++roundtripDone; }));
        })));
  }
  MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
      "ConcurrentMixedOps::roundtrip-wait"_ns,
      [&roundtripDone]() { return roundtripDone.load() == N; }));

  // Concurrent deletes.
  std::atomic<size_t> deleteDone{0};
  for (size_t i = 0; i < N; ++i) {
    const nsCString& c = colls[i];
    MOZ_ALWAYS_SUCCEEDS(NS_DispatchBackgroundTask(NS_NewRunnableFunction(
        "ConcurrentMixedOps::delete", [this, &c, &deleteDone]() {
          EXPECT_NS_SUCCEEDED(mService->DoDeleteDek(c));
          NS_DispatchToMainThread(
              NS_NewRunnableFunction("ConcurrentMixedOps::delete-done",
                                     [&deleteDone]() { ++deleteDone; }));
        })));
  }
  MOZ_ALWAYS_TRUE(
      SpinEventLoopUntil("ConcurrentMixedOps::delete-wait"_ns,
                         [&deleteDone]() { return deleteDone.load() == N; }));

  // Verify listCollections no longer contains any of them.
  RunOnBackground([&]() {
    auto remainingResult = mService->DoListDeks();
    ASSERT_TRUE(remainingResult.isOk());
    auto remaining = remainingResult.unwrap();
    for (const auto& c : colls) {
      for (const auto& r : remaining) {
        EXPECT_NE(r, c) << "Collection " << c.get()
                        << " should be gone but is still listed";
      }
    }
  });
}

Messung V0.5 in Prozent
C=84 H=73 G=78

¤ Dauer der Verarbeitung: 0.15 Sekunden  (vorverarbeitet am  2026-08-25) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=277311
#Domains=752002