// Copyright (C) 2013 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.google.gerrit.server.index; import static com.google.gerrit.reviewdb.client.Change.Status.ABANDONED; import static com.google.gerrit.reviewdb.client.Change.Status.DRAFT; import static com.google.gerrit.reviewdb.client.Change.Status.MERGED; import static com.google.gerrit.reviewdb.client.Change.Status.NEW; import static com.google.gerrit.reviewdb.client.Change.Status.SUBMITTED; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import com.google.common.collect.ImmutableList; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.server.query.AndPredicate; import com.google.gerrit.server.query.Predicate; import com.google.gerrit.server.query.QueryParseException; import com.google.gerrit.server.query.change.AndSource; import com.google.gerrit.server.query.change.BasicChangeRewrites; import com.google.gerrit.server.query.change.ChangeData; import com.google.gerrit.server.query.change.ChangeQueryBuilder; import com.google.gerrit.server.query.change.OrSource; import org.junit.Before; import org.junit.Test; import java.util.Arrays; import java.util.EnumSet; import java.util.Set; public class IndexRewriteTest { private FakeIndex index; private IndexCollection indexes; private ChangeQueryBuilder queryBuilder; private IndexRewriteImpl rewrite; @Before public void setUp() throws Exception { index = new FakeIndex(FakeIndex.V2); indexes = new IndexCollection(); indexes.setSearchIndex(index); queryBuilder = new FakeQueryBuilder(indexes); rewrite = new IndexRewriteImpl( indexes, new BasicChangeRewrites(null)); } @Test public void testIndexPredicate() throws Exception { Predicate<ChangeData> in = parse("file:a"); assertEquals(query(in), rewrite(in)); } @Test public void testNonIndexPredicate() throws Exception { Predicate<ChangeData> in = parse("foo:a"); assertSame(in, rewrite(in)); } @Test public void testIndexPredicates() throws Exception { Predicate<ChangeData> in = parse("file:a file:b"); assertEquals(query(in), rewrite(in)); } @Test public void testNonIndexPredicates() throws Exception { Predicate<ChangeData> in = parse("foo:a OR foo:b"); assertEquals(in, rewrite(in)); } @Test public void testOneIndexPredicate() throws Exception { Predicate<ChangeData> in = parse("foo:a file:b"); Predicate<ChangeData> out = rewrite(in); assertSame(AndSource.class, out.getClass()); assertEquals( ImmutableList.of(query(in.getChild(1)), in.getChild(0)), out.getChildren()); } @Test public void testThreeLevelTreeWithAllIndexPredicates() throws Exception { Predicate<ChangeData> in = parse("-status:abandoned (status:open OR status:merged)"); assertEquals( query(parse("status:new OR status:submitted OR status:draft OR status:merged")), rewrite.rewrite(in, 0)); } @Test public void testThreeLevelTreeWithSomeIndexPredicates() throws Exception { Predicate<ChangeData> in = parse("-foo:a (file:b OR file:c)"); Predicate<ChangeData> out = rewrite(in); assertEquals(AndSource.class, out.getClass()); assertEquals( ImmutableList.of(query(in.getChild(1)), in.getChild(0)), out.getChildren()); } @Test public void testMultipleIndexPredicates() throws Exception { Predicate<ChangeData> in = parse("file:a OR foo:b OR file:c OR foo:d"); Predicate<ChangeData> out = rewrite(in); assertSame(OrSource.class, out.getClass()); assertEquals(ImmutableList.of( query(Predicate.or(in.getChild(0), in.getChild(2))), in.getChild(1), in.getChild(3)), out.getChildren()); } @Test public void testIndexAndNonIndexPredicates() throws Exception { Predicate<ChangeData> in = parse("status:new bar:p file:a"); Predicate<ChangeData> out = rewrite(in); assertSame(AndSource.class, out.getClass()); assertEquals(ImmutableList.of( query(Predicate.and(in.getChild(0), in.getChild(2))), in.getChild(1)), out.getChildren()); } @Test public void testDuplicateCompoundNonIndexOnlyPredicates() throws Exception { Predicate<ChangeData> in = parse("(status:new OR status:draft) bar:p file:a"); Predicate<ChangeData> out = rewrite(in); assertSame(AndSource.class, out.getClass()); assertEquals(ImmutableList.of( query(Predicate.and(in.getChild(0), in.getChild(2))), in.getChild(1)), out.getChildren()); } @Test public void testDuplicateCompoundIndexOnlyPredicates() throws Exception { Predicate<ChangeData> in = parse("(status:new OR file:a) bar:p file:b"); Predicate<ChangeData> out = rewrite(in); assertSame(AndSource.class, out.getClass()); assertEquals(ImmutableList.of( query(Predicate.and(in.getChild(0), in.getChild(2))), in.getChild(1)), out.getChildren()); } @Test public void testLimit() throws Exception { Predicate<ChangeData> in = parse("file:a limit:3"); Predicate<ChangeData> out = rewrite(in); assertSame(AndSource.class, out.getClass()); assertEquals(ImmutableList.of( query(in.getChild(0), 3), in.getChild(1)), out.getChildren()); } @Test public void testStartIncreasesLimit() throws Exception { Predicate<ChangeData> f = parse("file:a"); Predicate<ChangeData> l = parse("limit:3"); Predicate<ChangeData> in = and(f, l); assertEquals(and(query(f, 3), l), rewrite.rewrite(in, 0)); assertEquals(and(query(f, 4), l), rewrite.rewrite(in, 1)); assertEquals(and(query(f, 5), l), rewrite.rewrite(in, 2)); } @Test public void testStartDoesNotExceedMaxLimit() throws Exception { Predicate<ChangeData> in = parse("file:a"); assertEquals(query(in), rewrite.rewrite(in, 0)); assertEquals(query(in), rewrite.rewrite(in, 1)); } @Test public void testGetPossibleStatus() throws Exception { assertEquals(EnumSet.allOf(Change.Status.class), status("file:a")); assertEquals(EnumSet.of(NEW), status("is:new")); assertEquals(EnumSet.of(SUBMITTED, DRAFT, MERGED, ABANDONED), status("-is:new")); assertEquals(EnumSet.of(NEW, MERGED), status("is:new OR is:merged")); EnumSet<Change.Status> none = EnumSet.noneOf(Change.Status.class); assertEquals(none, status("is:new is:merged")); assertEquals(none, status("(is:new is:draft) (is:merged is:submitted)")); assertEquals(none, status("(is:new is:draft) (is:merged is:submitted)")); assertEquals(EnumSet.of(MERGED, SUBMITTED), status("(is:new is:draft) OR (is:merged OR is:submitted)")); } @Test public void testUnsupportedIndexOperator() throws Exception { Predicate<ChangeData> in = parse("status:merged file:a"); assertEquals(query(in), rewrite(in)); indexes.setSearchIndex(new FakeIndex(FakeIndex.V1)); Predicate<ChangeData> out = rewrite(in); assertTrue(out instanceof AndPredicate); assertEquals(ImmutableList.of( query(in.getChild(0)), in.getChild(1)), out.getChildren()); } private Predicate<ChangeData> parse(String query) throws QueryParseException { return queryBuilder.parse(query); } @SafeVarargs private static AndSource and(Predicate<ChangeData>... preds) { return new AndSource(Arrays.asList(preds)); } private Predicate<ChangeData> rewrite(Predicate<ChangeData> in) throws QueryParseException { return rewrite.rewrite(in, 0); } private IndexedChangeQuery query(Predicate<ChangeData> p) throws QueryParseException { return query(p, IndexRewriteImpl.MAX_LIMIT); } private IndexedChangeQuery query(Predicate<ChangeData> p, int limit) throws QueryParseException { return new IndexedChangeQuery(index, p, limit); } private Set<Change.Status> status(String query) throws QueryParseException { return IndexRewriteImpl.getPossibleStatus(parse(query)); } }