You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by sa...@apache.org on 2016/07/21 13:37:58 UTC

[36/51] [abbrv] lucene-solr:apiv2: SOLR-9285: Fixed AIOOBE when using ValueSourceAugmenter in single node RTG

SOLR-9285: Fixed AIOOBE when using ValueSourceAugmenter in single node RTG


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/4123b3bf
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/4123b3bf
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/4123b3bf

Branch: refs/heads/apiv2
Commit: 4123b3bf26156227174ef3c417b36309c2beeb9a
Parents: 6f76ac1
Author: Chris Hostetter <ho...@apache.org>
Authored: Mon Jul 18 10:21:08 2016 -0700
Committer: Chris Hostetter <ho...@apache.org>
Committed: Mon Jul 18 10:21:08 2016 -0700

----------------------------------------------------------------------
 solr/CHANGES.txt                                |   2 +
 .../handler/component/RealTimeGetComponent.java |  79 ++-
 .../solr/response/transform/DocTransformer.java |  26 +-
 .../response/transform/DocTransformers.java     |  12 +
 .../transform/ValueSourceAugmenter.java         |  13 +-
 .../solr/cloud/TestCloudPseudoReturnFields.java |   5 +-
 .../apache/solr/cloud/TestRandomFlRTGCloud.java | 625 +++++++++++++++++++
 .../solr/search/TestPseudoReturnFields.java     |  57 +-
 8 files changed, 764 insertions(+), 55 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4123b3bf/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 4864925..bff2909 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -155,6 +155,8 @@ Bug Fixes
 * SOLR-7280: In cloud-mode sort the cores smartly before loading & limit threads to improve cluster stability
   (noble, Erick Erickson, shalin)
 
+* SOLR-9285: Fixed AIOOBE when using ValueSourceAugmenter in single node RTG (hossman)
+
 Optimizations
 ----------------------
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4123b3bf/solr/core/src/java/org/apache/solr/handler/component/RealTimeGetComponent.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/component/RealTimeGetComponent.java b/solr/core/src/java/org/apache/solr/handler/component/RealTimeGetComponent.java
index 78cebd3..9865a11 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/RealTimeGetComponent.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/RealTimeGetComponent.java
@@ -23,6 +23,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -58,13 +59,13 @@ import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.request.SolrQueryRequest;
-import org.apache.solr.response.BasicResultContext;
 import org.apache.solr.response.ResultContext;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.response.transform.DocTransformer;
 import org.apache.solr.schema.FieldType;
 import org.apache.solr.schema.IndexSchema;
 import org.apache.solr.schema.SchemaField;
+import org.apache.solr.search.DocList;
 import org.apache.solr.search.QParser;
 import org.apache.solr.search.ReturnFields;
 import org.apache.solr.search.SolrIndexSearcher;
@@ -192,12 +193,18 @@ public class RealTimeGetComponent extends SearchComponent
     UpdateLog ulog = core.getUpdateHandler().getUpdateLog();
 
     RefCounted<SolrIndexSearcher> searcherHolder = null;
+    
+    // this is initialized & set on the context *after* any searcher (re-)opening
+    ResultContext resultContext = null;
+    final DocTransformer transformer = rsp.getReturnFields().getTransformer();
+
+    // true in any situation where we have to use a realtime searcher rather then returning docs
+    // directly from the UpdateLog
+    final boolean mustUseRealtimeSearcher =
+      // if we have filters, we need to check those against the indexed form of the doc
+      (rb.getFilters() != null)
+      || ((null != transformer) && transformer.needsSolrIndexSearcher());
 
-    DocTransformer transformer = rsp.getReturnFields().getTransformer();
-    if (transformer != null) {
-      ResultContext context = new BasicResultContext(null, rsp.getReturnFields(), null, null, req);
-      transformer.setContext(context);
-    }
    try {
      SolrIndexSearcher searcher = null;
 
@@ -214,13 +221,13 @@ public class RealTimeGetComponent extends SearchComponent
            switch (oper) {
              case UpdateLog.ADD:
 
-               if (rb.getFilters() != null) {
-                 // we have filters, so we need to check those against the indexed form of the doc
+               if (mustUseRealtimeSearcher) {
                  if (searcherHolder != null) {
-                   // close handles to current searchers
+                   // close handles to current searchers & result context
                    searcher = null;
                    searcherHolder.decref();
                    searcherHolder = null;
+                   resultContext = null;
                  }
                  ulog.openRealtimeSearcher();  // force open a new realtime searcher
                  o = null;  // pretend we never found this record and fall through to use the searcher
@@ -228,7 +235,7 @@ public class RealTimeGetComponent extends SearchComponent
                }
 
                SolrDocument doc = toSolrDoc((SolrInputDocument)entry.get(entry.size()-1), core.getLatestSchema());
-               if(transformer!=null) {
+               if (transformer!=null) {
                  transformer.transform(doc, -1, 0); // unknown docID
                }
               docList.add(doc);
@@ -246,6 +253,7 @@ public class RealTimeGetComponent extends SearchComponent
        if (searcher == null) {
          searcherHolder = core.getRealtimeSearcher();
          searcher = searcherHolder.get();
+         // don't bother with ResultContext yet, we won't need it if doc doesn't match filters
        }
 
        int docid = -1;
@@ -267,12 +275,17 @@ public class RealTimeGetComponent extends SearchComponent
          }
        }
 
-
        if (docid < 0) continue;
+       
        Document luceneDocument = searcher.doc(docid, rsp.getReturnFields().getLuceneFieldNames());
        SolrDocument doc = toSolrDoc(luceneDocument,  core.getLatestSchema());
        searcher.decorateDocValueFields(doc, docid, searcher.getNonStoredDVs(true));
-       if( transformer != null ) {
+       if ( null != transformer) {
+         if (null == resultContext) {
+           // either first pass, or we've re-opened searcher - either way now we setContext
+           resultContext = new RTGResultContext(rsp.getReturnFields(), searcher, req);
+           transformer.setContext(resultContext);
+         }
          transformer.transform(doc, docid, 0);
        }
        docList.add(doc);
@@ -754,4 +767,46 @@ public class RealTimeGetComponent extends SearchComponent
     // TODO do we need to sort versions using PeerSync.absComparator?
     return new ArrayList<>(versionsToRet);
   }
+
+  /**
+   * A lite weight ResultContext for use with RTG requests that can point at Realtime Searchers
+   */
+  private static final class RTGResultContext extends ResultContext {
+    final ReturnFields returnFields;
+    final SolrIndexSearcher searcher;
+    final SolrQueryRequest req;
+    public RTGResultContext(ReturnFields returnFields, SolrIndexSearcher searcher, SolrQueryRequest req) {
+      this.returnFields = returnFields;
+      this.searcher = searcher;
+      this.req = req;
+    }
+    
+    /** @returns null */
+    public DocList getDocList() {
+      return null;
+    }
+    
+    public ReturnFields getReturnFields() {
+      return this.returnFields;
+    }
+    
+    public SolrIndexSearcher getSearcher() {
+      return this.searcher;
+    }
+    
+    /** @returns null */
+    public Query getQuery() {
+      return null;
+    }
+    
+    public SolrQueryRequest getRequest() {
+      return this.req;
+    }
+    
+    /** @returns null */
+    public Iterator<SolrDocument> getProcessedDocuments() {
+      return null;
+    }
+  }
+  
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4123b3bf/solr/core/src/java/org/apache/solr/response/transform/DocTransformer.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/response/transform/DocTransformer.java b/solr/core/src/java/org/apache/solr/response/transform/DocTransformer.java
index 11e3a9b..6111804 100644
--- a/solr/core/src/java/org/apache/solr/response/transform/DocTransformer.java
+++ b/solr/core/src/java/org/apache/solr/response/transform/DocTransformer.java
@@ -42,8 +42,10 @@ public abstract class DocTransformer {
   public abstract String getName();
 
   /**
-   * This is called before transform and sets
+   * This is called before {@link #transform} to provide context for any subsequent transformations.
+   *
    * @param context The {@link ResultContext} stores information about how the documents were produced.
+   * @see #needsSolrIndexSearcher
    */
   public void setContext( ResultContext context ) {
     this.context = context;
@@ -51,13 +53,31 @@ public abstract class DocTransformer {
   }
 
   /**
-   * This is where implementations do the actual work
+   * Indicates if this transformer requires access to the underlying index to perform it's functions.
+   *
+   * In some situations (notably RealTimeGet) this method <i>may</i> be called before {@link #setContext} 
+   * to determine if the transformer must be given a "full" ResultContext and accurate docIds 
+   * that can be looked up using {@link ResultContext#getSearcher}, or if optimizations can be taken 
+   * advantage of such that {@link ResultContext#getSearcher} <i>may</i> return null, and docIds passed to 
+   * {@link #transform} <i>may</i> be negative.
    *
+   * The default implementation always returns <code>false</code>.
+   * 
+   * @see ResultContext#getSearcher
+   * @see #transform
+   */
+  public boolean needsSolrIndexSearcher() { return false; }
+  
+  /**
+   * This is where implementations do the actual work.
+   * If implementations require a valid docId and index access, the {@link #needsSolrIndexSearcher} 
+   * method must return true
    *
    * @param doc The document to alter
-   * @param docid The Lucene internal doc id
+   * @param docid The Lucene internal doc id, or -1 in cases where the <code>doc</code> did not come from the index
    * @param score the score for this document
    * @throws IOException If there is a low-level I/O error.
+   * @see #needsSolrIndexSearcher
    */
   public abstract void transform(SolrDocument doc, int docid, float score) throws IOException;
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4123b3bf/solr/core/src/java/org/apache/solr/response/transform/DocTransformers.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/response/transform/DocTransformers.java b/solr/core/src/java/org/apache/solr/response/transform/DocTransformers.java
index e0b3a3c..7bb53ff 100644
--- a/solr/core/src/java/org/apache/solr/response/transform/DocTransformers.java
+++ b/solr/core/src/java/org/apache/solr/response/transform/DocTransformers.java
@@ -76,4 +76,16 @@ public class DocTransformers extends DocTransformer
       a.transform( doc, docid, score);
     }
   }
+
+  /** Returns true if and only if at least 1 child transformer returns true */
+  @Override
+  public boolean needsSolrIndexSearcher() {
+    for( DocTransformer kid : children ) {
+      if (kid.needsSolrIndexSearcher()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4123b3bf/solr/core/src/java/org/apache/solr/response/transform/ValueSourceAugmenter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/response/transform/ValueSourceAugmenter.java b/solr/core/src/java/org/apache/solr/response/transform/ValueSourceAugmenter.java
index 9ca0f2b..9edf826 100644
--- a/solr/core/src/java/org/apache/solr/response/transform/ValueSourceAugmenter.java
+++ b/solr/core/src/java/org/apache/solr/response/transform/ValueSourceAugmenter.java
@@ -21,7 +21,6 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.ReaderUtil;
 import org.apache.lucene.queries.function.FunctionValues;
 import org.apache.lucene.queries.function.ValueSource;
@@ -64,11 +63,9 @@ public class ValueSourceAugmenter extends DocTransformer
   public void setContext( ResultContext context ) {
     super.setContext(context);
     try {
-      IndexReader reader = qparser.getReq().getSearcher().getIndexReader();
-      readerContexts = reader.leaves();
+      searcher = context.getSearcher();
+      readerContexts = searcher.getIndexReader().leaves();
       docValuesArr = new FunctionValues[readerContexts.size()];
-
-      searcher = qparser.getReq().getSearcher();
       fcontext = ValueSource.newContext(searcher);
       this.valueSource.createWeight(fcontext, searcher);
     } catch (IOException e) {
@@ -76,13 +73,11 @@ public class ValueSourceAugmenter extends DocTransformer
     }
   }
 
-
   Map fcontext;
   SolrIndexSearcher searcher;
   List<LeafReaderContext> readerContexts;
   FunctionValues docValuesArr[];
 
-
   @Override
   public void transform(SolrDocument doc, int docid, float score) {
     // This is only good for random-access functions
@@ -103,6 +98,10 @@ public class ValueSourceAugmenter extends DocTransformer
       throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "exception at docid " + docid + " for valuesource " + valueSource, e);
     }
   }
+
+  /** Always returns true */
+  @Override
+  public boolean needsSolrIndexSearcher() { return true; }
   
   protected void setValue(SolrDocument doc, Object val) {
     if(val!=null) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4123b3bf/solr/core/src/test/org/apache/solr/cloud/TestCloudPseudoReturnFields.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestCloudPseudoReturnFields.java b/solr/core/src/test/org/apache/solr/cloud/TestCloudPseudoReturnFields.java
index bf56821..8553697 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestCloudPseudoReturnFields.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestCloudPseudoReturnFields.java
@@ -53,7 +53,10 @@ import org.apache.commons.lang.StringUtils;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 
-/** @see TestPseudoReturnFields */
+/** 
+ * @see TestPseudoReturnFields 
+ * @see TestRandomFlRTGCloud
+ */
 public class TestCloudPseudoReturnFields extends SolrCloudTestCase {
   
   private static final String DEBUG_LABEL = MethodHandles.lookup().lookupClass().getName();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4123b3bf/solr/core/src/test/org/apache/solr/cloud/TestRandomFlRTGCloud.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestRandomFlRTGCloud.java b/solr/core/src/test/org/apache/solr/cloud/TestRandomFlRTGCloud.java
new file mode 100644
index 0000000..8cf1129
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/cloud/TestRandomFlRTGCloud.java
@@ -0,0 +1,625 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.solr.cloud;
+
+import java.lang.invoke.MethodHandles;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Random;
+
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrServerException;
+import org.apache.solr.client.solrj.embedded.JettySolrRunner;
+import org.apache.solr.client.solrj.impl.HttpSolrClient;
+import org.apache.solr.client.solrj.impl.CloudSolrClient;
+import org.apache.solr.client.solrj.response.QueryResponse;
+
+import org.apache.solr.cloud.SolrCloudTestCase;
+
+import org.apache.solr.common.SolrDocument;
+import org.apache.solr.common.SolrDocumentList;
+import org.apache.solr.common.SolrInputDocument;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.NamedList;
+
+import org.apache.solr.util.RandomizeSSL;
+import org.apache.lucene.util.TestUtil;
+
+import org.apache.commons.io.FilenameUtils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+/** @see TestCloudPseudoReturnFields */
+@RandomizeSSL(clientAuth=0.0,reason="client auth uses too much RAM")
+public class TestRandomFlRTGCloud extends SolrCloudTestCase {
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+  private static final String DEBUG_LABEL = MethodHandles.lookup().lookupClass().getName();
+  private static final String COLLECTION_NAME = DEBUG_LABEL + "_collection";
+  
+  /** A basic client for operations at the cloud level, default collection will be set */
+  private static CloudSolrClient CLOUD_CLIENT;
+  /** One client per node */
+  private static ArrayList<HttpSolrClient> CLIENTS = new ArrayList<>(5);
+
+  /** Always included in fl so we can vet what doc we're looking at */
+  private static final FlValidator ID_VALIDATOR = new SimpleFieldValueValidator("id");
+  
+  /** 
+   * Types of things we will randomly ask for in fl param, and validate in response docs.
+   *
+   * This list starts out with the things we know concretely should work for any type of request,
+   * {@link #createMiniSolrCloudCluster} will add too it with additional validators that are expected 
+   * to work dependingon hte random cluster creation
+   * 
+   * @see #addRandomFlValidators
+   */
+  private static final List<FlValidator> FL_VALIDATORS = new ArrayList<>
+    // TODO: SOLR-9314: once all the known bugs are fixed, and this list can be constant
+    // regardless of single/multi node, change this to Collections.unmodifiableList
+    // (and adjust jdocs accordingly)
+    (Arrays.<FlValidator>asList(
+      // TODO: SOLR-9314: add more of these for other various transformers
+      //
+      // TODO: add a [docid] validator (blocked by SOLR-9288 & SOLR-9289)
+      //
+      new GlobValidator("*"),
+      new GlobValidator("*_i"),
+      new GlobValidator("*_s"),
+      new GlobValidator("a*"),
+      new SimpleFieldValueValidator("aaa_i"),
+      new SimpleFieldValueValidator("ccc_s"),
+      new NotIncludedValidator("bogus_unused_field_ss"),
+      new NotIncludedValidator("bogus_alias","bogus_alias:other_bogus_field_i"),
+      new NotIncludedValidator("explain_alias","explain_alias:[explain]"),
+      new NotIncludedValidator("score")));
+  
+  @BeforeClass
+  private static void createMiniSolrCloudCluster() throws Exception {
+
+    // Due to known bugs with some transformers in either multi vs single node, we want
+    // to test both possible cases explicitly and modify the List of FL_VALIDATORS we use accordingly:
+    //  - 50% runs use single node/shard a FL_VALIDATORS with all validators known to work on single node
+    //  - 50% runs use multi node/shard with FL_VALIDATORS only containing stuff that works in cloud
+    final boolean singleCoreMode = random().nextBoolean();
+    if (singleCoreMode) {
+      // these don't work in distrib cloud mode due to SOLR-9286
+      FL_VALIDATORS.addAll(Arrays.asList
+                           (new FunctionValidator("aaa_i"), // fq field
+                            new FunctionValidator("aaa_i", "func_aaa_alias"),
+                            new RenameFieldValueValidator("id", "my_id_alias"),
+                            new RenameFieldValueValidator("bbb_i", "my_int_field_alias"),
+                            new RenameFieldValueValidator("ddd_s", "my_str_field_alias")));
+    } else {
+      // No-Op
+      // No known transformers that only work in distrib cloud but fail in singleCoreMode
+
+    }
+    // TODO: SOLR-9314: programatically compare FL_VALIDATORS with all known transformers.
+    // (ala QueryEqualityTest) can't be done until we eliminate the need for "singleCodeMode"
+    // conditional logic (might still want 'singleCoreMode' on the MiniSolrCloudCluster side,
+    // but shouldn't have conditional FlValidators
+
+    // (asuming multi core multi replicas shouldn't matter (assuming multi node) ...
+    final int repFactor = singleCoreMode ? 1 : (usually() ? 1 : 2);
+    // ... but we definitely want to ensure forwarded requests to other shards work ...
+    final int numShards = singleCoreMode ? 1 : 2;
+    // ... including some forwarded requests from nodes not hosting a shard
+    final int numNodes = 1 + (singleCoreMode ? 0 : (numShards * repFactor));
+    
+    final String configName = DEBUG_LABEL + "_config-set";
+    final Path configDir = Paths.get(TEST_HOME(), "collection1", "conf");
+    
+    configureCluster(numNodes).addConfig(configName, configDir).configure();
+    
+    Map<String, String> collectionProperties = new HashMap<>();
+    collectionProperties.put("config", "solrconfig-tlog.xml");
+    collectionProperties.put("schema", "schema-psuedo-fields.xml");
+
+    assertNotNull(cluster.createCollection(COLLECTION_NAME, numShards, repFactor,
+                                           configName, null, null, collectionProperties));
+    
+    CLOUD_CLIENT = cluster.getSolrClient();
+    CLOUD_CLIENT.setDefaultCollection(COLLECTION_NAME);
+
+    waitForRecoveriesToFinish(CLOUD_CLIENT);
+
+    for (JettySolrRunner jetty : cluster.getJettySolrRunners()) {
+      CLIENTS.add(getHttpSolrClient(jetty.getBaseUrl() + "/" + COLLECTION_NAME + "/"));
+    }
+  }
+
+  @AfterClass
+  private static void afterClass() throws Exception {
+    CLOUD_CLIENT.close(); CLOUD_CLIENT = null;
+    for (HttpSolrClient client : CLIENTS) {
+      client.close();
+    }
+    CLIENTS = null;
+  }
+  
+  public void testRandomizedUpdatesAndRTGs() throws Exception {
+
+    final int maxNumDocs = atLeast(100);
+    final int numSeedDocs = random().nextInt(maxNumDocs / 10); // at most ~10% of the max possible docs
+    final int numIters = atLeast(maxNumDocs * 10);
+    final SolrInputDocument[] knownDocs = new SolrInputDocument[maxNumDocs];
+
+    log.info("Starting {} iters by seeding {} of {} max docs",
+             numIters, numSeedDocs, maxNumDocs);
+
+    int itersSinceLastCommit = 0;
+    for (int i = 0; i < numIters; i++) {
+      itersSinceLastCommit = maybeCommit(random(), itersSinceLastCommit, numIters);
+
+      if (i < numSeedDocs) {
+        // first N iters all we worry about is seeding
+        knownDocs[i] = addRandomDocument(i);
+      } else {
+        assertOneIter(knownDocs);
+      }
+    }
+  }
+
+  /** 
+   * Randomly chooses to do a commit, where the probability of doing so increases the longer it's been since 
+   * a commit was done.
+   *
+   * @returns <code>0</code> if a commit was done, else <code>itersSinceLastCommit + 1</code>
+   */
+  private static int maybeCommit(final Random rand, final int itersSinceLastCommit, final int numIters) throws IOException, SolrServerException {
+    final float threshold = itersSinceLastCommit / numIters;
+    if (rand.nextFloat() < threshold) {
+      log.info("COMMIT");
+      assertEquals(0, getRandClient(rand).commit().getStatus());
+      return 0;
+    }
+    return itersSinceLastCommit + 1;
+  }
+  
+  private void assertOneIter(final SolrInputDocument[] knownDocs) throws IOException, SolrServerException {
+    // we want to occasionally test more then one doc per RTG
+    final int numDocsThisIter = TestUtil.nextInt(random(), 1, atLeast(2));
+    int numDocsThisIterThatExist = 0;
+    
+    // pick some random docIds for this iteration and ...
+    final int[] docIds = new int[numDocsThisIter];
+    for (int i = 0; i < numDocsThisIter; i++) {
+      docIds[i] = random().nextInt(knownDocs.length);
+      if (null != knownDocs[docIds[i]]) {
+        // ...check how many already exist
+        numDocsThisIterThatExist++;
+      }
+    }
+
+    // we want our RTG requests to occasionally include missing/deleted docs,
+    // but that's not the primary focus of the test, so weight the odds accordingly
+    if (random().nextInt(numDocsThisIter + 2) <= numDocsThisIterThatExist) {
+
+      if (0 < TestUtil.nextInt(random(), 0, 13)) {
+        log.info("RTG: numDocsThisIter={} numDocsThisIterThatExist={}, docIds={}",
+                 numDocsThisIter, numDocsThisIterThatExist, docIds);
+        assertRTG(knownDocs, docIds);
+      } else {
+        // sporadically delete some docs instead of doing an RTG
+        log.info("DEL: numDocsThisIter={} numDocsThisIterThatExist={}, docIds={}",
+                 numDocsThisIter, numDocsThisIterThatExist, docIds);
+        assertDelete(knownDocs, docIds);
+      }
+    } else {
+      log.info("UPD: numDocsThisIter={} numDocsThisIterThatExist={}, docIds={}",
+               numDocsThisIter, numDocsThisIterThatExist, docIds);
+      assertUpdate(knownDocs, docIds);
+    }
+  }
+
+  /**
+   * Does some random indexing of the specified docIds and adds them to knownDocs
+   */
+  private void assertUpdate(final SolrInputDocument[] knownDocs, final int[] docIds) throws IOException, SolrServerException {
+    
+    for (final int docId : docIds) {
+      // TODO: this method should also do some atomic update operations (ie: "inc" and "set")
+      // (but make sure to eval the updates locally as well before modifying knownDocs)
+      knownDocs[docId] = addRandomDocument(docId);
+    }
+  }
+  
+  /**
+   * Deletes the docIds specified and asserts the results are valid, updateing knownDocs accordingly
+   */
+  private void assertDelete(final SolrInputDocument[] knownDocs, final int[] docIds) throws IOException, SolrServerException {
+    List<String> ids = new ArrayList<>(docIds.length);
+    for (final int docId : docIds) {
+      ids.add("" + docId);
+      knownDocs[docId] = null;
+    }
+    assertEquals("Failed delete: " + docIds, 0, getRandClient(random()).deleteById(ids).getStatus());
+  }
+  
+  /**
+   * Adds one randomly generated document with the specified docId, asserting success, and returns 
+   * the document added
+   */
+  private SolrInputDocument addRandomDocument(final int docId) throws IOException, SolrServerException {
+    final SolrClient client = getRandClient(random());
+    
+    final SolrInputDocument doc = sdoc("id", "" + docId,
+                                       "aaa_i", random().nextInt(),
+                                       "bbb_i", random().nextInt(),
+                                       //
+                                       "ccc_s", TestUtil.randomSimpleString(random()),
+                                       "ddd_s", TestUtil.randomSimpleString(random()),
+                                       //
+                                       "axx_i", random().nextInt(),
+                                       "ayy_i", random().nextInt(),
+                                       "azz_s", TestUtil.randomSimpleString(random()));
+    
+    log.info("ADD: {} = {}", docId, doc);
+    assertEquals(0, client.add(doc).getStatus());
+    return doc;
+  }
+
+  
+  /**
+   * Does one or more RTG request for the specified docIds with a randomized fl &amp; fq params, asserting
+   * that the returned document (if any) makes sense given the expected SolrInputDocuments
+   */
+  private void assertRTG(final SolrInputDocument[] knownDocs, final int[] docIds) throws IOException, SolrServerException {
+    final SolrClient client = getRandClient(random());
+    // NOTE: not using SolrClient.getById or getByIds because we want to force choice of "id" vs "ids" params
+    final ModifiableSolrParams params = params("qt","/get");
+    
+    // TODO: fq testing blocked by SOLR-9308
+    //
+    // // random fq -- nothing fancy, secondary concern for our test
+    final Integer FQ_MAX = null;                                           // TODO: replace this...
+    // final Integer FQ_MAX = usually() ? null : random().nextInt();       //       ... with this
+    // if (null != FQ_MAX) {
+    //   params.add("fq", "aaa_i:[* TO " + FQ_MAX + "]");
+    // }
+    // TODO: END
+    
+    final Set<FlValidator> validators = new HashSet<>();
+    validators.add(ID_VALIDATOR); // always include id so we can be confident which doc we're looking at
+    addRandomFlValidators(random(), validators);
+    FlValidator.addFlParams(validators, params);
+    
+    final List<String> idsToRequest = new ArrayList<>(docIds.length);
+    final List<SolrInputDocument> docsToExpect = new ArrayList<>(docIds.length);
+    for (int docId : docIds) {
+      // every docId will be included in the request
+      idsToRequest.add("" + docId);
+      
+      // only docs that should actually exist and match our (optional) filter will be expected in response
+      if (null != knownDocs[docId]) {
+        Integer filterVal = (Integer) knownDocs[docId].getFieldValue("aaa_i");
+        if (null == FQ_MAX || ((null != filterVal) && filterVal.intValue() <= FQ_MAX.intValue())) {
+          docsToExpect.add(knownDocs[docId]);
+        }
+      }
+    }
+
+    // even w/only 1 docId requested, the response format can vary depending on how we request it
+    final boolean askForList = random().nextBoolean() || (1 != idsToRequest.size());
+    if (askForList) {
+      if (1 == idsToRequest.size()) {
+        // have to be careful not to try to use "multi" 'id' params with only 1 docId
+        // with a single docId, the only way to ask for a list is with the "ids" param
+        params.add("ids", idsToRequest.get(0));
+      } else {
+        if (random().nextBoolean()) {
+          // each id in it's own param
+          for (String id : idsToRequest) {
+            params.add("id",id);
+          }
+        } else {
+          // add one or more comma seperated ids params
+          params.add(buildCommaSepParams(random(), "ids", idsToRequest));
+        }
+      }
+    } else {
+      assert 1 == idsToRequest.size();
+      params.add("id",idsToRequest.get(0));
+    }
+    
+    final QueryResponse rsp = client.query(params);
+    assertNotNull(params.toString(), rsp);
+
+    final SolrDocumentList docs = getDocsFromRTGResponse(askForList, rsp);
+    assertNotNull(params + " => " + rsp, docs);
+    
+    assertEquals("num docs mismatch: " + params + " => " + docsToExpect + " vs " + docs,
+                 docsToExpect.size(), docs.size());
+    
+    // NOTE: RTG makes no garuntees about the order docs will be returned in when multi requested
+    for (SolrDocument actual : docs) {
+      try {
+        int actualId = Integer.parseInt(actual.getFirstValue("id").toString());
+        final SolrInputDocument expected = knownDocs[actualId];
+        assertNotNull("expected null doc but RTG returned: " + actual, expected);
+        
+        Set<String> expectedFieldNames = new HashSet<>();
+        for (FlValidator v : validators) {
+          expectedFieldNames.addAll(v.assertRTGResults(validators, expected, actual));
+        }
+        // ensure only expected field names are in the actual document
+        Set<String> actualFieldNames = new HashSet<>(actual.getFieldNames());
+        assertEquals("More actual fields then expected", expectedFieldNames, actualFieldNames);
+      } catch (AssertionError ae) {
+        throw new AssertionError(params + " => " + actual + ": " + ae.getMessage(), ae);
+      }
+    }
+  }
+
+  /** 
+   * trivial helper method to deal with diff response structure between using a single 'id' param vs
+   * 2 or more 'id' params (or 1 or more 'ids' params).
+   *
+   * NOTE: <code>expectList</code> is currently ignored due to SOLR-9309 -- instead best efforst are made to
+   * return a synthetic list based on whatever can be found in the response.
+   *
+   * @return List from response, or a synthetic one created from single response doc if 
+   * <code>expectList</code> was false; May be empty; May be null if response included null list.
+   */
+  private static SolrDocumentList getDocsFromRTGResponse(final boolean expectList, final QueryResponse rsp) {
+    // TODO: blocked by SOLR-9309 (once this can be fixed, update jdocs)
+    if (null != rsp.getResults()) { // TODO: replace this..
+    // if (expectList) {            // TODO: ...with this tighter check.
+      return rsp.getResults();
+    }
+    
+    // else: expect single doc, make our own list...
+    
+    final SolrDocumentList result = new SolrDocumentList();
+    NamedList<Object> raw = rsp.getResponse();
+    Object doc = raw.get("doc");
+    if (null != doc) {
+      result.add((SolrDocument) doc);
+      result.setNumFound(1);
+    }
+    return result;
+  }
+    
+  /** 
+   * returns a random SolrClient -- either a CloudSolrClient, or an HttpSolrClient pointed 
+   * at a node in our cluster 
+   */
+  public static SolrClient getRandClient(Random rand) {
+    int numClients = CLIENTS.size();
+    int idx = TestUtil.nextInt(rand, 0, numClients);
+    return (idx == numClients) ? CLOUD_CLIENT : CLIENTS.get(idx);
+  }
+
+  public static void waitForRecoveriesToFinish(CloudSolrClient client) throws Exception {
+    assert null != client.getDefaultCollection();
+    AbstractDistribZkTestBase.waitForRecoveriesToFinish(client.getDefaultCollection(),
+                                                        client.getZkStateReader(),
+                                                        true, true, 330);
+  }
+
+  /** 
+   * abstraction for diff types of things that can be added to an 'fl' param that can validate
+   * the results are correct compared to an expected SolrInputDocument
+   */
+  private interface FlValidator {
+    
+    /** Given a list of FlValidators, adds one or more fl params that corrispond to the entire set */
+    public static void addFlParams(final Collection<FlValidator> validators, final ModifiableSolrParams params) {
+      final List<String> fls = new ArrayList<>(validators.size());
+      for (FlValidator v : validators) {
+        fls.add(v.getFlParam());
+      }
+      params.add(buildCommaSepParams(random(), "fl", fls));
+    }
+    
+    /** 
+     * Must return a non null String that can be used in an fl param -- either by itself, 
+     * or with other items separated by commas
+     */
+    public String getFlParam();
+
+    /** 
+     * Given the expected document and the actual document returned from an RTG, this method
+     * should assert that relative to what {@link #getFlParam} returns, the actual document contained
+     * what it should relative to the expected document.
+     *
+     * @param validators all validators in use for this request, including the current one
+     * @param expected a document containing the expected fields &amp; values that should be in the index
+     * @param actual A document that was returned by an RTG request
+     * @return A set of "field names" in the actual document that this validator expected.
+     */
+    public Collection<String> assertRTGResults(final Collection<FlValidator> validators,
+                                               final SolrInputDocument expected,
+                                               final SolrDocument actual);
+  }
+
+  private abstract static class FieldValueValidator implements FlValidator {
+    protected final String expectedFieldName;
+    protected final String actualFieldName;
+    public FieldValueValidator(final String expectedFieldName, final String actualFieldName) {
+      this.expectedFieldName = expectedFieldName;
+      this.actualFieldName = actualFieldName;
+    }
+    public abstract String getFlParam();
+    public Collection<String> assertRTGResults(final Collection<FlValidator> validators,
+                                               final SolrInputDocument expected,
+                                               final SolrDocument actual) {
+      assertEquals(expectedFieldName + " vs " + actualFieldName,
+                   expected.getFieldValue(expectedFieldName), actual.getFirstValue(actualFieldName));
+      return Collections.<String>singleton(actualFieldName);
+    }
+  }
+  
+  private static class SimpleFieldValueValidator extends FieldValueValidator {
+    public SimpleFieldValueValidator(final String fieldName) {
+      super(fieldName, fieldName);
+    }
+    public String getFlParam() { return expectedFieldName; }
+  }
+  
+  private static class RenameFieldValueValidator extends FieldValueValidator {
+    /** @see GlobValidator */
+    public String getRealFieldName() { return expectedFieldName; }
+    public RenameFieldValueValidator(final String origFieldName, final String alias) {
+      super(origFieldName, alias);
+    }
+    public String getFlParam() { return actualFieldName + ":" + expectedFieldName; }
+  }
+
+  /** Trivial validator of a ValueSourceAugmenter */
+  private static class FunctionValidator implements FlValidator {
+    private static String func(String fieldName) {
+      return "add(1.3,sub("+fieldName+","+fieldName+"))";
+    }
+    protected final String fl;
+    protected final String resultKey;
+    protected final String fieldName;
+    public FunctionValidator(final String fieldName) {
+      this(func(fieldName), fieldName, func(fieldName));
+    }
+    public FunctionValidator(final String fieldName, final String resultKey) {
+      this(resultKey + ":" + func(fieldName), fieldName, resultKey);
+    }
+    private FunctionValidator(final String fl, final String fieldName, final String resultKey) {
+      this.fl = fl;
+      this.resultKey = resultKey;
+      this.fieldName = fieldName;
+    }
+    public String getFlParam() { return fl; }
+    public Collection<String> assertRTGResults(final Collection<FlValidator> validators,
+                                               final SolrInputDocument expected,
+                                               final SolrDocument actual) {
+      final Object origVal = expected.getFieldValue(fieldName);
+      assertTrue("this validator only works on numeric fields: " + origVal, origVal instanceof Number);
+      
+      assertEquals(fl, 1.3F, actual.getFirstValue(resultKey));
+      return Collections.<String>singleton(resultKey);
+    }
+  }
+
+  /** 
+   * Glob based validator.
+   * This class checks that every field in the expected doc exists in the actual doc with the expected 
+   * value -- with special exceptions for fields that are "renamed" with an alias. 
+   *
+   * By design, fields that are aliased are "moved" unless the original field name was explicitly included 
+   * in the fl, globs don't count.
+   *
+   * @see RenameFieldValueValidator
+   */
+  private static class GlobValidator implements FlValidator {
+    private final String glob;
+    public GlobValidator(final String glob) {
+      this.glob = glob;
+    }
+    private final Set<String> matchingFieldsCache = new HashSet<>();
+    
+    public String getFlParam() { return glob; }
+    
+    private boolean matchesGlob(final String fieldName) {
+      if ( FilenameUtils.wildcardMatch(fieldName, glob) ) {
+        matchingFieldsCache.add(fieldName); // Don't calculate it again
+        return true;
+      }
+      return false;
+    }
+                                
+    public Collection<String> assertRTGResults(final Collection<FlValidator> validators,
+                                               final SolrInputDocument expected,
+                                               final SolrDocument actual) {
+
+      final Set<String> renamed = new HashSet<>(validators.size());
+      for (FlValidator v : validators) {
+        if (v instanceof RenameFieldValueValidator) {
+          renamed.add(((RenameFieldValueValidator)v).getRealFieldName());
+        }
+      }
+      
+      // every real field name matching the glob that is not renamed should be in the results
+      Set<String> result = new HashSet<>(expected.getFieldNames().size());
+      for (String f : expected.getFieldNames()) {
+        if ( matchesGlob(f) && (! renamed.contains(f) ) ) {
+          result.add(f);
+          assertEquals(glob + " => " + f, expected.getFieldValue(f), actual.getFirstValue(f));
+        }
+      }
+      return result;
+    }
+  }
+  
+  /** 
+   * for things like "score" and "[explain]" where we explicitly expect what we ask for in the fl
+   * to <b>not</b> be returned when using RTG.
+   */
+  private static class NotIncludedValidator implements FlValidator {
+    private final String fieldName;
+    private final String fl;
+    public NotIncludedValidator(final String fl) {
+      this(fl, fl);
+    }
+    public NotIncludedValidator(final String fieldName, final String fl) {
+      this.fieldName = fieldName;
+      this.fl = fl;
+    }
+    public String getFlParam() { return fl; }
+    public Collection<String> assertRTGResults(final Collection<FlValidator> validators,
+                                               final SolrInputDocument expected,
+                                               final SolrDocument actual) {
+      assertEquals(fl, null, actual.getFirstValue(fieldName));
+      return Collections.emptySet();
+    }
+  }
+
+  /** helper method for adding a random number (may be 0) of items from {@link #FL_VALIDATORS} */
+  private static void addRandomFlValidators(final Random r, final Set<FlValidator> validators) {
+    List<FlValidator> copyToShuffle = new ArrayList<>(FL_VALIDATORS);
+    Collections.shuffle(copyToShuffle, r);
+    final int numToReturn = r.nextInt(copyToShuffle.size());
+    validators.addAll(copyToShuffle.subList(0, numToReturn + 1));
+  }
+
+  /**
+   * Given an ordered list of values to include in a (key) param, randomly groups them (ie: comma seperated) 
+   * into actual param key=values which are returned as a new SolrParams instance
+   */
+  private static SolrParams buildCommaSepParams(final Random rand, final String key, Collection<String> values) {
+    ModifiableSolrParams result = new ModifiableSolrParams();
+    List<String> copy = new ArrayList<>(values);
+    while (! copy.isEmpty()) {
+      List<String> slice = copy.subList(0, random().nextInt(1 + copy.size()));
+      result.add(key,String.join(",",slice));
+      slice.clear();
+    }
+    return result;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4123b3bf/solr/core/src/test/org/apache/solr/search/TestPseudoReturnFields.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/TestPseudoReturnFields.java b/solr/core/src/test/org/apache/solr/search/TestPseudoReturnFields.java
index 8b85ba0..68f0773 100644
--- a/solr/core/src/test/org/apache/solr/search/TestPseudoReturnFields.java
+++ b/solr/core/src/test/org/apache/solr/search/TestPseudoReturnFields.java
@@ -95,7 +95,6 @@ public class TestPseudoReturnFields extends SolrTestCaseJ4 {
     );
   }
 
-  @AwaitsFix(bugUrl="https://issues.apache.org/jira/browse/SOLR-9285")
   public void testMultiValuedRTG() throws Exception {
 
     // single value int using alias that matches multivalued dynamic field - via RTG
@@ -247,7 +246,6 @@ public class TestPseudoReturnFields extends SolrTestCaseJ4 {
             );
   }
   
-  @AwaitsFix(bugUrl="https://issues.apache.org/jira/browse/SOLR-9285")
   public void testFunctionsRTG() throws Exception {
     // if we use RTG (committed or otherwise) functions should behave the same
     for (String id : Arrays.asList("42","99")) {
@@ -286,7 +284,6 @@ public class TestPseudoReturnFields extends SolrTestCaseJ4 {
             );
   }
 
-  @AwaitsFix(bugUrl="https://issues.apache.org/jira/browse/SOLR-9285")
   public void testFunctionsAndExplicitRTG() throws Exception {
     // shouldn't matter if we use RTG (committed or otherwise)
     for (String id : Arrays.asList("42","99")) {
@@ -346,10 +343,7 @@ public class TestPseudoReturnFields extends SolrTestCaseJ4 {
     
   }
   
-  @AwaitsFix(bugUrl="https://issues.apache.org/jira/browse/SOLR-9285")
   public void testFunctionsAndScoreRTG() throws Exception {
-    // NOTE: once this test is fixed to pass, testAugmentersRTG should also be updated to test a abs(val_i)
-
     // if we use RTG (committed or otherwise) score should be ignored
     for (String id : Arrays.asList("42","99")) {
       for (SolrParams p : Arrays.asList(params("fl","score","fl","log(val_i)","fl","abs(val_i)"),
@@ -360,7 +354,7 @@ public class TestPseudoReturnFields extends SolrTestCaseJ4 {
                 req(p, "qt","/get","id",id, "wt","xml")
                 ,"count(//doc)=1"
                 ,"//doc/double[@name='log(val_i)']"
-                ,"//doc/float[@name='abs(val_i)']"
+                ,"//doc/float[@name='abs(val_i)'][.='1.0']"
                 ,"//doc[count(*)=2]"
                 );
       }
@@ -561,20 +555,21 @@ public class TestPseudoReturnFields extends SolrTestCaseJ4 {
     // behavior shouldn't matter if we are committed or uncommitted
     for (String id : Arrays.asList("42","99")) {
       // NOTE: once testDocIdAugmenterRTG can pass, [docid] should be tested here as well.
-      // NOTE: once testFunctionsAndScoreRTG can pass, abs(val_i) should be tested here as well
-      for (SolrParams p : Arrays.asList(params("fl","[shard],[explain],x_alias:[value v=10 t=int]"),
-                                        params("fl","[shard]","fl","[explain],x_alias:[value v=10 t=int]"),
-                                        params("fl","[shard]","fl","[explain]","fl","x_alias:[value v=10 t=int]"))) {
+      for (SolrParams p : Arrays.asList
+             (params("fl","[shard],[explain],x_alias:[value v=10 t=int],abs(val_i)"),
+              params("fl","[shard],abs(val_i)","fl","[explain],x_alias:[value v=10 t=int]"),
+              params("fl","[shard]","fl","[explain],x_alias:[value v=10 t=int]","fl","abs(val_i)"),
+              params("fl","[shard]","fl","[explain]","fl","x_alias:[value v=10 t=int]","fl","abs(val_i)"))) {
         assertQ(id + ": " + p,
                 req(p, "qt","/get","id",id, "wt","xml")
                 ,"count(//doc)=1"
                 // ,"//doc/int[@name='[docid]']" // TODO
-                // ,"//doc/gloat[@name='abs(val_i)']" // TODO
+                ,"//doc/float[@name='abs(val_i)'][.='1.0']"
                 ,"//doc/str[@name='[shard]'][.='[not a shard request]']"
                 // RTG: [explain] should be missing (ignored)
                 ,"//doc/int[@name='x_alias'][.=10]"
                 
-                ,"//doc[count(*)=2]"
+                ,"//doc[count(*)=3]"
                 );
       }
     }
@@ -601,20 +596,20 @@ public class TestPseudoReturnFields extends SolrTestCaseJ4 {
     // behavior shouldn't matter if we are committed or uncommitted
     for (String id : Arrays.asList("42","99")) {
       // NOTE: once testDocIdAugmenterRTG can pass, [docid] should be tested here as well.
-      // NOTE: once testFunctionsAndScoreRTG can pass, abs(val_i) should be tested here as well
-      for (SolrParams p : Arrays.asList(params("fl","id,[explain],x_alias:[value v=10 t=int]"),
-                                        params("fl","id","fl","[explain],x_alias:[value v=10 t=int]"),
-                                        params("fl","id","fl","[explain]","fl","x_alias:[value v=10 t=int]"))) {
+      for (SolrParams p : Arrays.asList
+             (params("fl","id,[explain],x_alias:[value v=10 t=int],abs(val_i)"),
+              params("fl","id,abs(val_i)","fl","[explain],x_alias:[value v=10 t=int]"),
+              params("fl","id","fl","[explain]","fl","x_alias:[value v=10 t=int]","fl","abs(val_i)"))) {
         assertQ(id + ": " + p,
                 req(p, "qt","/get","id",id, "wt","xml")
                 ,"count(//doc)=1"
                 ,"//doc/str[@name='id']"
                 // ,"//doc/int[@name='[docid]']" // TODO
-                // ,"//doc/gloat[@name='abs(val_i)']" // TODO
+                ,"//doc/float[@name='abs(val_i)'][.='1.0']"
                 // RTG: [explain] should be missing (ignored)
                 ,"//doc/int[@name='x_alias'][.=10]"
                 
-                ,"//doc[count(*)=2]"
+                ,"//doc[count(*)=3]"
               );
       }
     }
@@ -652,29 +647,28 @@ public class TestPseudoReturnFields extends SolrTestCaseJ4 {
     // if we use RTG (committed or otherwise) score should be ignored
     for (String id : Arrays.asList("42","99")) {
       // NOTE: once testDocIdAugmenterRTG can pass, [docid] should be tested here as well.
-      // NOTE: once testFunctionsAndScoreRTG can pass, abs(val_i) should be tested here as well
       assertQ(id,
               req("qt","/get","id",id, "wt","xml",
-                  "fl","x_alias:[value v=10 t=int],score")
+                  "fl","x_alias:[value v=10 t=int],score,abs(val_i)")
               // ,"//doc/int[@name='[docid]']" // TODO
-              // ,"//doc/gloat[@name='abs(val_i)']" // TODO
+              ,"//doc/float[@name='abs(val_i)'][.='1.0']"
               ,"//doc/int[@name='x_alias'][.=10]"
               
-              ,"//doc[count(*)=1]"
+              ,"//doc[count(*)=2]"
               );
-      for (SolrParams p : Arrays.asList(params("fl","x_alias:[value v=10 t=int],[explain],score"),
-                                        params("fl","x_alias:[value v=10 t=int],[explain]","fl","score"),
-                                        params("fl","x_alias:[value v=10 t=int]","fl","[explain]","fl","score"))) {
+      for (SolrParams p : Arrays.asList(params("fl","x_alias:[value v=10 t=int],[explain],score,abs(val_i)"),
+                                        params("fl","x_alias:[value v=10 t=int],[explain]","fl","score,abs(val_i)"),
+                                        params("fl","x_alias:[value v=10 t=int]","fl","[explain]","fl","score","fl","abs(val_i)"))) {
         
         assertQ(p.toString(),
                 req(p, "qt","/get","id",id, "wt","xml")
                 
                 // ,"//doc/int[@name='[docid]']" // TODO
-                // ,"//doc/gloat[@name='abs(val_i)']" // TODO
+                ,"//doc/float[@name='abs(val_i)'][.='1.0']"
                 ,"//doc/int[@name='x_alias'][.=10]"
                 // RTG: [explain] and score should be missing (ignored)
                 
-                ,"//doc[count(*)=1]"
+                ,"//doc[count(*)=2]"
                 );
       }
     }
@@ -720,8 +714,7 @@ public class TestPseudoReturnFields extends SolrTestCaseJ4 {
     // NOTE: 'ssto' is the missing one
     final List<String> fl = Arrays.asList
       // NOTE: once testDocIdAugmenterRTG can pass, [docid] should be tested here as well.
-      // NOTE: once testFunctionsAndScoreRTG can pass, abs(val_i) should be tested here as well
-      ("id","[explain]","score","val_*","subj*");
+      ("id","[explain]","score","val_*","subj*","abs(val_i)");
     
     final int iters = atLeast(random, 10);
     for (int i = 0; i< iters; i++) {
@@ -742,11 +735,11 @@ public class TestPseudoReturnFields extends SolrTestCaseJ4 {
                   ,"count(//doc)=1"
                   ,"//doc/str[@name='id']"
                   // ,"//doc/int[@name='[docid]']" // TODO
-                  // ,"//doc/gloat[@name='abs(val_i)']" // TODO
+                  ,"//doc/float[@name='abs(val_i)'][.='1.0']"
                   // RTG: [explain] and score should be missing (ignored)
                   ,"//doc/int[@name='val_i'][.=1]"
                   ,"//doc/str[@name='subject']"
-                  ,"//doc[count(*)=3]"
+                  ,"//doc[count(*)=4]"
                   );
         }
       }