Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bc33c8db authored by Alexander Dorokhine's avatar Alexander Dorokhine Committed by Android (Google) Code Review
Browse files

Merge "Create test utils and shims for porting AppSearch CTS tests."

parents 818939cd ab03e08b
Loading
Loading
Loading
Loading
+24 −0
Original line number Diff line number Diff line
// Copyright (C) 2020 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.
java_library {
    name: "AppSearchTestUtils",
    srcs: ["java/**/*.java"],
    libs: [
        "androidx.test.ext.junit",
        "framework",
        "framework-appsearch",
        "guava",
        "truth-prebuilt",
    ],
}
+145 −0
Original line number Diff line number Diff line
/*
 * Copyright 2020 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.
 */

package com.android.server.appsearch.testing;

import android.annotation.NonNull;
import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.AppSearchManager;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSession;
import android.app.appsearch.BatchResultCallback;
import android.app.appsearch.GenericDocument;
import android.app.appsearch.GetByUriRequest;
import android.app.appsearch.PutDocumentsRequest;
import android.app.appsearch.RemoveByUriRequest;
import android.app.appsearch.SearchResults;
import android.app.appsearch.SearchSpec;
import android.app.appsearch.SetSchemaRequest;
import android.content.Context;

import androidx.test.core.app.ApplicationProvider;

import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * This test class adapts the AppSearch Framework API to ListenableFuture, so it can be tested via
 * a consistent interface.
 * @hide
 */
public class AppSearchSessionShim {
    private final AppSearchSession mAppSearchSession;
    private final ExecutorService mExecutor;

    @NonNull
    public static ListenableFuture<AppSearchResult<AppSearchSessionShim>> createSearchSession(
            @NonNull AppSearchManager.SearchContext searchContext) {
        Context context = ApplicationProvider.getApplicationContext();
        AppSearchManager appSearchManager = context.getSystemService(AppSearchManager.class);
        SettableFuture<AppSearchResult<AppSearchSession>> future = SettableFuture.create();
        ExecutorService executor = Executors.newCachedThreadPool();
        appSearchManager.createSearchSession(searchContext, executor, future::set);
        return Futures.transform(future, (instance) -> {
            if (!instance.isSuccess()) {
                return AppSearchResult.newFailedResult(
                        instance.getResultCode(), instance.getErrorMessage());
            }
            AppSearchSession searchSession = instance.getResultValue();
            AppSearchSessionShim shim = new AppSearchSessionShim(searchSession, executor);
            return AppSearchResult.newSuccessfulResult(shim);
        }, executor);
    }

    private AppSearchSessionShim(
            @NonNull AppSearchSession session, @NonNull ExecutorService executor) {
        mAppSearchSession = Preconditions.checkNotNull(session);
        mExecutor = Preconditions.checkNotNull(executor);
    }

    @NonNull
    public ListenableFuture<AppSearchResult<Void>> setSchema(@NonNull SetSchemaRequest request) {
        SettableFuture<AppSearchResult<Void>> future = SettableFuture.create();
        mAppSearchSession.setSchema(request, mExecutor, future::set);
        return future;
    }

    @NonNull
    public ListenableFuture<AppSearchBatchResult<String, Void>> putDocuments(
            @NonNull PutDocumentsRequest request) {
        SettableFuture<AppSearchBatchResult<String, Void>> future = SettableFuture.create();
        mAppSearchSession.putDocuments(
                request, mExecutor, new BatchResultCallbackAdapter<>(future));
        return future;
    }

    @NonNull
    public ListenableFuture<AppSearchBatchResult<String, GenericDocument>> getByUri(
            @NonNull GetByUriRequest request) {
        SettableFuture<AppSearchBatchResult<String, GenericDocument>> future =
                SettableFuture.create();
        mAppSearchSession.getByUri(request, mExecutor, new BatchResultCallbackAdapter<>(future));
        return future;
    }

    @NonNull
    public SearchResultsShim query(
            @NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
        SearchResults searchResults =
                mAppSearchSession.query(queryExpression, searchSpec, mExecutor);
        return new SearchResultsShim(searchResults);
    }

    @NonNull
    public ListenableFuture<AppSearchBatchResult<String, Void>> removeByUri(
            @NonNull RemoveByUriRequest request) {
        SettableFuture<AppSearchBatchResult<String, Void>> future = SettableFuture.create();
        mAppSearchSession.removeByUri(request, mExecutor, new BatchResultCallbackAdapter<>(future));
        return future;
    }

    @NonNull
    public ListenableFuture<AppSearchResult<Void>> removeByQuery(
            @NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
        SettableFuture<AppSearchResult<Void>> future = SettableFuture.create();
        mAppSearchSession.removeByQuery(queryExpression, searchSpec, mExecutor, future::set);
        return future;
    }

    private static final class BatchResultCallbackAdapter<K, V>
            implements BatchResultCallback<K, V> {
        private final SettableFuture<AppSearchBatchResult<K, V>> mFuture;

        BatchResultCallbackAdapter(SettableFuture<AppSearchBatchResult<K, V>> future) {
            mFuture = future;
        }

        @Override
        public void onResult(AppSearchBatchResult<K, V> result) {
            mFuture.set(result);
        }

        @Override
        public void onSystemError(Throwable t) {
            mFuture.setException(t);
        }
    }
}
+78 −0
Original line number Diff line number Diff line
/*
 * Copyright 2020 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.
 */

package com.android.server.appsearch.testing;

import android.annotation.NonNull;
import android.app.appsearch.AppSearchManager;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.GlobalSearchSession;
import android.app.appsearch.SearchResults;
import android.app.appsearch.SearchSpec;
import android.content.Context;

import androidx.test.core.app.ApplicationProvider;

import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * This test class adapts the AppSearch Framework API to ListenableFuture, so it can be tested via
 * a consistent interface.
 * @hide
 */
public class GlobalSearchSessionShim {
    private final GlobalSearchSession mGlobalSearchSession;
    private final ExecutorService mExecutor;

    @NonNull
    public static ListenableFuture<AppSearchResult<GlobalSearchSessionShim>>
            createGlobalSearchSession() {
        Context context = ApplicationProvider.getApplicationContext();
        AppSearchManager appSearchManager = context.getSystemService(AppSearchManager.class);
        SettableFuture<AppSearchResult<GlobalSearchSession>> future = SettableFuture.create();
        ExecutorService executor = Executors.newCachedThreadPool();
        appSearchManager.createGlobalSearchSession(executor, future::set);
        return Futures.transform(future, (instance) -> {
            if (!instance.isSuccess()) {
                return AppSearchResult.newFailedResult(
                        instance.getResultCode(), instance.getErrorMessage());
            }
            GlobalSearchSession searchSession = instance.getResultValue();
            GlobalSearchSessionShim shim = new GlobalSearchSessionShim(searchSession, executor);
            return AppSearchResult.newSuccessfulResult(shim);
        }, executor);
    }

    private GlobalSearchSessionShim(
            @NonNull GlobalSearchSession session, @NonNull ExecutorService executor) {
        mGlobalSearchSession = Preconditions.checkNotNull(session);
        mExecutor = Preconditions.checkNotNull(executor);
    }

    @NonNull
    public SearchResultsShim query(
            @NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
        SearchResults searchResults =
                mGlobalSearchSession.query(queryExpression, searchSpec, mExecutor);
        return new SearchResultsShim(searchResults);
    }
}
+54 −0
Original line number Diff line number Diff line
/*
 * Copyright 2020 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.
 */

package com.android.server.appsearch.testing;

import android.annotation.NonNull;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.SearchResult;
import android.app.appsearch.SearchResults;

import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;

import java.io.Closeable;
import java.util.List;

/**
 * This test class adapts the AppSearch Framework API to ListenableFuture, so it can be tested via
 * a consistent interface.
 * @hide
 */
public class SearchResultsShim implements Closeable {
    private final SearchResults mSearchResults;

    SearchResultsShim(@NonNull SearchResults searchResults) {
        mSearchResults = Preconditions.checkNotNull(searchResults);
    }

    @NonNull
    public ListenableFuture<AppSearchResult<List<SearchResult>>> getNextPage() {
        SettableFuture<AppSearchResult<List<SearchResult>>> future = SettableFuture.create();
        mSearchResults.getNextPage(future::set);
        return future;
    }

    @Override
    public void close() {
        mSearchResults.close();
    }
}
+102 −0
Original line number Diff line number Diff line
/*
 * Copyright 2020 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.
 */

package com.android.server.appsearch.testing;

import static com.google.common.truth.Truth.assertThat;

import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.AppSearchManager;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.GenericDocument;
import android.app.appsearch.GetByUriRequest;
import android.app.appsearch.SearchResult;
import android.app.appsearch.SetSchemaRequest;
import android.content.Context;

import com.google.common.collect.ImmutableList;

import junit.framework.AssertionFailedError;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;

public class AppSearchTestUtils {

    // List of databases that may be used in tests. Keeping them in a centralized location helps
    // #cleanup know which databases to clear.
    public static final String DEFAULT_DATABASE = AppSearchManager.DEFAULT_DATABASE_NAME;
    public static final String DB_1 = "testDb1";
    public static final String DB_2 = "testDb2";

    public static void cleanup(Context context) throws Exception {
        List<String> databases = ImmutableList.of(DEFAULT_DATABASE, DB_1, DB_2);
        for (String database : databases) {
            AppSearchSessionShim session = checkIsResultSuccess(
                    AppSearchSessionShim.createSearchSession(
                            new AppSearchManager.SearchContext.Builder()
                                    .setDatabaseName(database).build()));
            checkIsResultSuccess(session.setSchema(
                    new SetSchemaRequest.Builder().setForceOverride(true).build()));
        }
    }

    public static <V> V checkIsResultSuccess(Future<AppSearchResult<V>> future) throws Exception {
        AppSearchResult<V> result = future.get();
        if (!result.isSuccess()) {
            throw new AssertionFailedError("AppSearchResult not successful: " + result);
        }
        return result.getResultValue();
    }

    public static <K, V> AppSearchBatchResult<K, V> checkIsBatchResultSuccess(
            Future<AppSearchBatchResult<K, V>> future) throws Exception {
        AppSearchBatchResult<K, V> result = future.get();
        if (!result.isSuccess()) {
            throw new AssertionFailedError("AppSearchBatchResult not successful: " + result);
        }
        return result;
    }

    public static List<GenericDocument> doGet(
            AppSearchSessionShim session, String namespace, String... uris) throws Exception {
        AppSearchBatchResult<String, GenericDocument> result = checkIsBatchResultSuccess(
                session.getByUri(
                        new GetByUriRequest.Builder()
                                .setNamespace(namespace).addUri(uris).build()));
        assertThat(result.getSuccesses()).hasSize(uris.length);
        assertThat(result.getFailures()).isEmpty();
        List<GenericDocument> list = new ArrayList<>(uris.length);
        for (String uri : uris) {
            list.add(result.getSuccesses().get(uri));
        }
        return list;
    }

    public static List<GenericDocument> convertSearchResultsToDocuments(
            SearchResultsShim searchResults) throws Exception {
        List<SearchResult> results = checkIsResultSuccess(searchResults.getNextPage());
        List<GenericDocument> documents = new ArrayList<>();
        while (results.size() > 0) {
            for (SearchResult result : results) {
                documents.add(result.getDocument());
            }
            results = checkIsResultSuccess(searchResults.getNextPage());
        }
        return documents;
    }
}
Loading