private Task AssertExpirationInRangeAsync(Context context, string cacheNamespace, StrongFingerprint strongFingerprint, DateTime expectedLow, DateTime expectedHigh)
        {
            // Create a bare BuildCache client so that the value read is not thwarted by some intermediate cache layer (like Redis)
            Func <DisposableDirectory, ICache> createCheckerCacheFunc =
                dir => CreateBareBuildCache(dir, cacheNamespace, FileSystem, Logger, BackingOption.WriteThrough, ItemStorageOption);

            Func <ICacheSession, Task> checkFunc = async(ICacheSession checkSession) =>
            {
                // Bare BuildCache clients should only produce BuildCacheSessions
                BuildCacheSession buildCacheSession = checkSession as BuildCacheSession;
                Assert.NotNull(buildCacheSession);

                // Raw expiration is only visible to the adapter, not through the ICacheSession APIs.
                // This also has the nice (non-)side-effect of not updating the expiration as part of *this* read.
                IContentHashListAdapter buildCacheHttpClientAdapter       = buildCacheSession.ContentHashListAdapter;
                ObjectResult <ContentHashListWithCacheMetadata> getResult = await buildCacheHttpClientAdapter.GetContentHashListAsync(context, cacheNamespace, strongFingerprint);

                Assert.True(getResult.Succeeded);
                Assert.NotNull(getResult.Data);
                DateTime?rawExpiration = getResult.Data.GetRawExpirationTimeUtc();
                Assert.NotNull(rawExpiration);

                // Verify that the raw expiration (i.e. the value's TTL w/o consideration for content existence or whether the value might be replaced) is visible and correct
                TimeSpan assertionTolerance = TimeSpan.FromHours(1); // The test time + the simulator's clock skew hopefully won't exceed 1 hour.
                Assert.InRange(rawExpiration.Value, expectedLow - assertionTolerance, expectedHigh + assertionTolerance);
            };

            return(RunTestAsync(context, checkFunc, createCheckerCacheFunc));
        }
        /// <inheritdoc />
        public Task <GetContentHashListResult> GetContentHashListAsync(
            Context context,
            StrongFingerprint strongFingerprint,
            CancellationToken cts,
            UrgencyHint urgencyHint)
        {
            return(GetContentHashListCall.RunAsync(Tracer.MemoizationStoreTracer, context, strongFingerprint, async() =>
            {
                // Check for pre-fetched data
                ContentHashListWithDeterminism contentHashListWithDeterminism;

                if (ContentHashListWithDeterminismCache.Instance.TryGetValue(
                        CacheNamespace, strongFingerprint, out contentHashListWithDeterminism))
                {
                    Tracer.RecordUseOfPrefetchedContentHashList();
                    FingerprintTracker.Track(
                        strongFingerprint,
                        contentHashListWithDeterminism.Determinism.ExpirationUtc);
                    return new GetContentHashListResult(contentHashListWithDeterminism);
                }

                // No pre-fetched data. Need to query the server.
                ObjectResult <ContentHashListWithCacheMetadata> responseObject =
                    await ContentHashListAdapter.GetContentHashListAsync(context, CacheNamespace, strongFingerprint).ConfigureAwait(false);

                if (!responseObject.Succeeded)
                {
                    return new GetContentHashListResult(responseObject);
                }

                ContentHashListWithCacheMetadata response = responseObject.Data;
                if (response.ContentHashListWithDeterminism.ContentHashList == null)
                {
                    // Miss
                    return new GetContentHashListResult(new ContentHashListWithDeterminism(null, CacheDeterminism.None));
                }

                GetContentHashListResult unpackResult = UnpackContentHashListWithDeterminismAfterGet(response, CacheId);
                if (!unpackResult.Succeeded)
                {
                    return unpackResult;
                }

                SealIfNecessaryAfterGet(context, strongFingerprint, response);
                FingerprintTracker.Track(strongFingerprint, response.GetRawExpirationTimeUtc());
                return new GetContentHashListResult(unpackResult.ContentHashListWithDeterminism);
            }));
        }