Skip to content

Commit

Permalink
Firestore: Convert testing_hooks_util.h/cc from C++ to Objective-C in…
Browse files Browse the repository at this point in the history
… FSTTestingHooks.h/mm (#11639)
  • Loading branch information
dconeybe authored Aug 3, 2023
1 parent 5a7af33 commit a4ee260
Show file tree
Hide file tree
Showing 6 changed files with 291 additions and 140 deletions.
32 changes: 16 additions & 16 deletions Firestore/Example/Firestore.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

43 changes: 19 additions & 24 deletions Firestore/Example/Tests/Integration/API/FIRQueryTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@
#import "Firestore/Example/Tests/Util/FSTEventAccumulator.h"
#import "Firestore/Example/Tests/Util/FSTHelpers.h"
#import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"

#include "Firestore/core/test/unit/testutil/testing_hooks_util.h"
#import "Firestore/Example/Tests/Util/FSTTestingHooks.h"

namespace {

Expand Down Expand Up @@ -1178,9 +1177,6 @@ - (void)testOrderByEquality {
}

- (void)testResumingAQueryShouldUseBloomFilterToAvoidFullRequery {
using firebase::firestore::testutil::CaptureExistenceFilterMismatches;
using firebase::firestore::util::TestingHooks;

// TODO(b/291365820): Stop skipping this test when running against the Firestore emulator once
// the emulator is improved to include a bloom filter in the existence filter messages that it
// sends.
Expand Down Expand Up @@ -1243,11 +1239,11 @@ - (void)testResumingAQueryShouldUseBloomFilterToAvoidFullRequery {
// Resume the query and save the resulting snapshot for verification.
// Use some internal testing hooks to "capture" the existence filter mismatches to verify that
// Watch sent a bloom filter, and it was used to avert a full requery.
FIRQuerySnapshot *querySnapshot2;
std::vector<TestingHooks::ExistenceFilterMismatchInfo> existence_filter_mismatches =
CaptureExistenceFilterMismatches([&] {
__block FIRQuerySnapshot *querySnapshot2;
NSArray<FSTTestingHooksExistenceFilterMismatchInfo *> *existenceFilterMismatches =
[FSTTestingHooks captureExistenceFilterMismatchesDuringBlock:^{
querySnapshot2 = [self readDocumentSetForRef:collRef source:FIRFirestoreSourceDefault];
});
}];

// Verify that the snapshot from the resumed query contains the expected documents; that is,
// that it contains the 50 documents that were _not_ deleted.
Expand Down Expand Up @@ -1279,33 +1275,32 @@ - (void)testResumingAQueryShouldUseBloomFilterToAvoidFullRequery {

// Verify that Watch sent an existence filter with the correct counts when the query was
// resumed.
XCTAssertEqual(existence_filter_mismatches.size(), size_t{1},
XCTAssertEqual(existenceFilterMismatches.count, 1u,
@"Watch should have sent exactly 1 existence filter");
const TestingHooks::ExistenceFilterMismatchInfo &existenceFilterMismatchInfo =
existence_filter_mismatches[0];
XCTAssertEqual(existenceFilterMismatchInfo.local_cache_count, 100);
XCTAssertEqual(existenceFilterMismatchInfo.existence_filter_count, 50);
FSTTestingHooksExistenceFilterMismatchInfo *existenceFilterMismatchInfo =
existenceFilterMismatches[0];
XCTAssertEqual(existenceFilterMismatchInfo.localCacheCount, 100);
XCTAssertEqual(existenceFilterMismatchInfo.existenceFilterCount, 50);

// Verify that Watch sent a valid bloom filter.
const absl::optional<TestingHooks::BloomFilterInfo> &bloom_filter =
existence_filter_mismatches[0].bloom_filter;
XCTAssertTrue(bloom_filter.has_value(),
"Watch should have included a bloom filter in the existence filter");
XCTAssertGreaterThan(bloom_filter->hash_count, 0);
XCTAssertGreaterThan(bloom_filter->bitmap_length, 0);
XCTAssertGreaterThan(bloom_filter->padding, 0);
XCTAssertLessThan(bloom_filter->padding, 8);
FSTTestingHooksBloomFilter *bloomFilter = existenceFilterMismatchInfo.bloomFilter;
XCTAssertNotNil(bloomFilter,
"Watch should have included a bloom filter in the existence filter");
XCTAssertGreaterThan(bloomFilter.hashCount, 0);
XCTAssertGreaterThan(bloomFilter.bitmapLength, 0);
XCTAssertGreaterThan(bloomFilter.padding, 0);
XCTAssertLessThan(bloomFilter.padding, 8);

// Verify that the bloom filter was successfully used to avert a full requery. If a false
// positive occurred then retry the entire test. Although statistically rare, false positives
// are expected to happen occasionally. When a false positive _does_ happen, just retry the test
// with a different set of documents. If that retry _also_ experiences a false positive, then
// fail the test because that is so improbable that something must have gone wrong.
if (attemptNumber == 1 && !bloom_filter->applied) {
if (attemptNumber == 1 && !bloomFilter.applied) {
continue;
}

XCTAssertTrue(bloom_filter->applied,
XCTAssertTrue(bloomFilter.applied,
@"The bloom filter should have been successfully applied with attemptNumber=%@",
@(attemptNumber));

Expand Down
102 changes: 102 additions & 0 deletions Firestore/Example/Tests/Util/FSTTestingHooks.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright 2023 Google LLC
*
* 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.
*/

// NOTE: For Swift compatibility, please keep this header Objective-C only.
// Swift cannot interact with any C++ definitions.
#import <Foundation/Foundation.h>

@class FIRDocumentReference;

NS_ASSUME_NONNULL_BEGIN

#pragma mark - FSTTestingHooksBloomFilter

/**
* Information about the bloom filter provided by Watch in the ExistenceFilter message's
* `unchanged_names` field.
*/
@interface FSTTestingHooksBloomFilter : NSObject

- (instancetype)init __attribute__((unavailable("instances cannot be created directly")));

/**
* Whether a full requery was averted by using the bloom filter. If false, then something happened,
* such as a false positive, to prevent using the bloom filter to avoid a full requery.
*/
@property(nonatomic, readonly) BOOL applied;

/** The number of hash functions used in the bloom filter. */
@property(nonatomic, readonly) int hashCount;

/** The number of bytes in the bloom filter's bitmask. */
@property(nonatomic, readonly) int bitmapLength;

/** The number of bits of padding in the last byte of the bloom filter. */
@property(nonatomic, readonly) int padding;

@end // @interface FSTTestingHooksBloomFilter

#pragma mark - FSTTestingHooksExistenceFilterMismatchInfo

/**
* Information about an existence filter mismatch.
*/
@interface FSTTestingHooksExistenceFilterMismatchInfo : NSObject

- (instancetype)init __attribute__((unavailable("instances cannot be created directly")));

/** The number of documents that matched the query in the local cache. */
@property(nonatomic, readonly) int localCacheCount;

/**
* The number of documents that matched the query on the server, as specified in the
* `ExistenceFilter` message's `count` field.
*/
@property(nonatomic, readonly) int existenceFilterCount;

/**
* Information about the bloom filter provided by Watch in the ExistenceFilter message's
* `unchanged_names` field. If nil, then that means that Watch did _not_ provide a bloom filter.
*/
@property(nonatomic, readonly, nullable) FSTTestingHooksBloomFilter* bloomFilter;

@end

#pragma mark - FSTTestingHooks

/**
* Manages "testing hooks", hooks into the internals of the SDK to verify internal state and events
* during integration tests.
*/
@interface FSTTestingHooks : NSObject

- (instancetype)init __attribute__((unavailable("instances cannot be created")));

/**
* Captures all existence filter mismatches in the Watch 'Listen' stream that occur during the
* execution of the given block.
*
* @param block The block to execute; during the execution of this block all existence filter
* mismatches will be captured.
*
* @return the captured existence filter mismatches.
*/
+ (NSArray<FSTTestingHooksExistenceFilterMismatchInfo*>*)
captureExistenceFilterMismatchesDuringBlock:(void (^)())block;

@end

NS_ASSUME_NONNULL_END
154 changes: 154 additions & 0 deletions Firestore/Example/Tests/Util/FSTTestingHooks.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* Copyright 2023 Google LLC
*
* 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.
*/

#import "Firestore/Example/Tests/Util/FSTTestingHooks.h"

#import "FIRDocumentReference.h"

#include <memory>
#include <string>
#include <utility>

#include "Firestore/core/src/api/listener_registration.h"
#include "Firestore/core/src/remote/bloom_filter.h"
#include "Firestore/core/src/util/defer.h"
#include "Firestore/core/src/util/string_apple.h"
#include "Firestore/core/src/util/string_format.h"
#include "Firestore/core/src/util/testing_hooks.h"
#include "Firestore/core/test/unit/testutil/async_testing.h"
#include "absl/types/optional.h"

using firebase::firestore::api::ListenerRegistration;
using firebase::firestore::remote::BloomFilter;
using firebase::firestore::testutil::AsyncAccumulator;
using firebase::firestore::util::Defer;
using firebase::firestore::util::MakeStringView;
using firebase::firestore::util::StringFormat;
using firebase::firestore::util::TestingHooks;

#pragma mark - FSTTestingHooksBloomFilter extension

// Add private "init" methods to FSTTestingHooksBloomFilter.
@interface FSTTestingHooksBloomFilter ()

- (instancetype)initWithApplied:(BOOL)applied
hashCount:(int)hashCount
bitmapLength:(int)bitmapLength
padding:(int)padding NS_DESIGNATED_INITIALIZER;

- (instancetype)initWithBloomFilterInfo:(const TestingHooks::BloomFilterInfo&)bloomFilterInfo;

@end

#pragma mark - FSTTestingHooksBloomFilter implementation

@implementation FSTTestingHooksBloomFilter

- (instancetype)initWithApplied:(BOOL)applied
hashCount:(int)hashCount
bitmapLength:(int)bitmapLength
padding:(int)padding {
if (self = [super init]) {
_applied = applied;
_hashCount = hashCount;
_bitmapLength = bitmapLength;
_padding = padding;
}
return self;
}

- (instancetype)initWithBloomFilterInfo:(const TestingHooks::BloomFilterInfo&)bloomFilterInfo {
return [self initWithApplied:bloomFilterInfo.applied
hashCount:bloomFilterInfo.hash_count
bitmapLength:bloomFilterInfo.bitmap_length
padding:bloomFilterInfo.padding];
}

@end

#pragma mark - FSTTestingHooksExistenceFilterMismatchInfo extension

// Add private "init" methods to FSTTestingHooksExistenceFilterMismatchInfo.
@interface FSTTestingHooksExistenceFilterMismatchInfo ()

- (instancetype)initWithLocalCacheCount:(int)localCacheCount
existenceFilterCount:(int)existenceFilterCount
bloomFilter:(nullable FSTTestingHooksBloomFilter*)bloomFilter
NS_DESIGNATED_INITIALIZER;

- (instancetype)initWithExistenceFilterMismatchInfo:
(const TestingHooks::ExistenceFilterMismatchInfo&)existenceFilterMismatchInfo;

@end

#pragma mark - FSTTestingHooksExistenceFilterMismatchInfo implementation

@implementation FSTTestingHooksExistenceFilterMismatchInfo

- (instancetype)initWithLocalCacheCount:(int)localCacheCount
existenceFilterCount:(int)existenceFilterCount
bloomFilter:(nullable FSTTestingHooksBloomFilter*)bloomFilter {
if (self = [super init]) {
_localCacheCount = localCacheCount;
_existenceFilterCount = existenceFilterCount;
_bloomFilter = bloomFilter;
}
return self;
}

- (instancetype)initWithExistenceFilterMismatchInfo:
(const TestingHooks::ExistenceFilterMismatchInfo&)existenceFilterMismatchInfo {
FSTTestingHooksBloomFilter* bloomFilter;
if (existenceFilterMismatchInfo.bloom_filter.has_value()) {
bloomFilter = [[FSTTestingHooksBloomFilter alloc]
initWithBloomFilterInfo:existenceFilterMismatchInfo.bloom_filter.value()];
} else {
bloomFilter = nil;
}

return [self initWithLocalCacheCount:existenceFilterMismatchInfo.local_cache_count
existenceFilterCount:existenceFilterMismatchInfo.existence_filter_count
bloomFilter:bloomFilter];
}

@end

#pragma mark - FSTTestingHooks implementation

@implementation FSTTestingHooks

+ (NSArray<FSTTestingHooksExistenceFilterMismatchInfo*>*)
captureExistenceFilterMismatchesDuringBlock:(void (^)())block {
auto accumulator = AsyncAccumulator<TestingHooks::ExistenceFilterMismatchInfo>::NewInstance();

TestingHooks& testing_hooks = TestingHooks::GetInstance();
std::shared_ptr<ListenerRegistration> registration =
testing_hooks.OnExistenceFilterMismatch(accumulator->AsCallback());
Defer unregister_callback([registration]() { registration->Remove(); });

block();

NSMutableArray<FSTTestingHooksExistenceFilterMismatchInfo*>* mismatches =
[[NSMutableArray alloc] init];
while (!accumulator->IsEmpty()) {
[mismatches addObject:[[FSTTestingHooksExistenceFilterMismatchInfo alloc]
initWithExistenceFilterMismatchInfo:accumulator->Shift()]];
}

return mismatches;
}

@end
57 changes: 0 additions & 57 deletions Firestore/core/test/unit/testutil/testing_hooks_util.cc

This file was deleted.

Loading

0 comments on commit a4ee260

Please sign in to comment.