[smart-select:] import recent changes from google3.

(1) Strip some Google-specific links (http://cr/150751377)

(2) Improve and add tests for registration mechanism:
http://cr/150679054, http://cr/150792974, http://cr/150899682

(3) Extra regression tests: http://cr/150939660

Test: everything builds, no significant change of .so size.
Change-Id: I177ba12b8d0cdcd615619f8ecd960cbcb0b26a77
diff --git a/common/memory_image/data-store.proto b/common/memory_image/data-store.proto
index 808d3a6..68e914a 100644
--- a/common/memory_image/data-store.proto
+++ b/common/memory_image/data-store.proto
@@ -19,9 +19,6 @@
 // string, with minimal parsing; after deserialization, all chunks of bytes
 // start at aligned addresses (aligned = multiple of an address specified at
 // build time).
-//
-// Note: underlying implementation uses the memory images from
-// http://g3doc/nlp/saft/components/common/mobile/memory_image/g3doc/index.md
 
 syntax = "proto2";
 option optimize_for = LITE_RUNTIME;
diff --git a/common/memory_image/embedding-network-params-from-image.h b/common/memory_image/embedding-network-params-from-image.h
index 0a410ff..feb4817 100644
--- a/common/memory_image/embedding-network-params-from-image.h
+++ b/common/memory_image/embedding-network-params-from-image.h
@@ -30,8 +30,7 @@
 //
 // In this context, a memory image is like an EmbeddingNetworkProto, but with
 // all repeated weights (>99% of the size) directly usable (with no parsing
-// required).  For general info on memory images, see
-// http://g3doc/nlp/saft/components/common/mobile/memory_image/g3doc/index.md
+// required).
 class EmbeddingNetworkParamsFromImage : public EmbeddingNetworkParams {
  public:
   // Constructs an EmbeddingNetworkParamsFromImage, using the memory image that
diff --git a/common/memory_image/memory-image-common.h b/common/memory_image/memory-image-common.h
index d49cdd3..2e84116 100644
--- a/common/memory_image/memory-image-common.h
+++ b/common/memory_image/memory-image-common.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-// Common utils for memory images.  For more info on memory images, see
-// http://g3doc/common/memory_image/g3doc/index.md
+// Common utils for memory images.
 
 #ifndef LIBTEXTCLASSIFIER_COMMON_MEMORY_IMAGE_MEMORY_IMAGE_COMMON_H_
 #define LIBTEXTCLASSIFIER_COMMON_MEMORY_IMAGE_MEMORY_IMAGE_COMMON_H_
diff --git a/common/memory_image/memory-image.proto b/common/memory_image/memory-image.proto
index 9ff12db..f6b624c 100644
--- a/common/memory_image/memory-image.proto
+++ b/common/memory_image/memory-image.proto
@@ -13,9 +13,6 @@
 // limitations under the License.
 
 // Protos for "memory images".
-//
-// For info on memory images, see
-// http://g3doc/nlp/saft/components/common/mobile/memory_image/g3doc/index.md
 
 syntax = "proto2";
 option optimize_for = LITE_RUNTIME;
diff --git a/common/registry.h b/common/registry.h
index 6f77b28..377e5fb 100644
--- a/common/registry.h
+++ b/common/registry.h
@@ -28,9 +28,13 @@
 //  // Abstract function that takes a double and returns a double.
 //  class Function : public RegisterableClass<Function> {
 //   public:
+//    virtual ~Function() {}
 //    virtual double Evaluate(double x) = 0;
 //  };
 //
+//  // Should be inside namespace libtextclassifier::nlp_core.
+//  TC_DECLARE_CLASS_REGISTRY_NAME(Function);
+//
 // Notice the inheritance from RegisterableClass<Function>.  RegisterableClass
 // is defined by this file (registry.h).  Under the hood, this inheritanace
 // defines a "registry" that maps names (zero-terminated arrays of chars) to
@@ -39,26 +43,27 @@
 // to be a .cc file, as it defines some static data):
 //
 //  // Inside function.cc
+//  // Should be inside namespace libtextclassifier::nlp_core.
 //  TC_DEFINE_CLASS_REGISTRY_NAME("function", Function);
 //
 // Now, let's define a few concrete Functions: e.g.,
 //
 //   class Cos : public Function {
 //    public:
-//     double Evaluate(double x) { return cos(x); }
+//     double Evaluate(double x) override { return cos(x); }
 //     TC_DEFINE_REGISTRATION_METHOD("cos", Cos);
 //   };
 //
 //   class Exp : public Function {
 //    public:
-//     double Evaluate(double x) { return exp(x); }
+//     double Evaluate(double x) override { return exp(x); }
 //     TC_DEFINE_REGISTRATION_METHOD("sin", Sin);
 //   };
 //
 // Each concrete Function implementation should have (in the public section) the
 // macro
 //
-//   TC_DEFINE_REGISTRATION_METHOD(base_class, "name", implementation_class);
+//   TC_DEFINE_REGISTRATION_METHOD("name", implementation_class);
 //
 // This defines a RegisterClass static method that, when invoked, associates
 // "name" with a factory method that creates instances of implementation_class.
@@ -76,22 +81,25 @@
 // interesting if the Function name is not statically known (i.e.,
 // read from an input proto:
 //
-//   std::unique_ptr<Function> f.reset(Function::Create("cos"));
+//   std::unique_ptr<Function> f(Function::Create("cos"));
 //   double result = f->Evaluate(arg);
 //
 // NOTE: the same binary can use this mechanism for different APIs.  E.g., one
 // can also have (in the binary with Function, Sin, Cos, etc):
 //
-// class IntFunction : RegisterableClass<IntFunction> {
+// class IntFunction : public RegisterableClass<IntFunction> {
 //  public:
+//   virtual ~IntFunction() {}
 //   virtual int Evaluate(int k) = 0;
 // };
 //
+// TC_DECLARE_CLASS_REGISTRY_NAME(IntFunction);
+//
 // TC_DEFINE_CLASS_REGISTRY_NAME("int function", IntFunction);
 //
 // class Inc : public IntFunction {
 //  public:
-//   int Evaluate(int k) { return k + 1; }
+//   int Evaluate(int k) override { return k + 1; }
 //   TC_DEFINE_REGISTRATION_METHOD("inc", Inc);
 // };
 //
@@ -259,6 +267,12 @@
   }
 
 // Defines the human-readable name of the registry associated with base_class.
+#define TC_DECLARE_CLASS_REGISTRY_NAME(base_class)             \
+  template <>                                                  \
+  const char ::libtextclassifier::nlp_core::RegisterableClass< \
+      base_class>::kRegistryName[]
+
+// Defines the human-readable name of the registry associated with base_class.
 #define TC_DEFINE_CLASS_REGISTRY_NAME(registry_name, base_class) \
   template <>                                                    \
   const char ::libtextclassifier::nlp_core::RegisterableClass<   \
diff --git a/lang_id/light-sentence-features.h b/lang_id/light-sentence-features.h
index 758f23d..a140f65 100644
--- a/lang_id/light-sentence-features.h
+++ b/lang_id/light-sentence-features.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef LIBTEXTCLASSIFIER_LANG_ID_LIGH_SENTENCE_FEATURES_H_
-#define LIBTEXTCLASSIFIER_LANG_ID_LIGH_SENTENCE_FEATURES_H_
+#ifndef LIBTEXTCLASSIFIER_LANG_ID_LIGHT_SENTENCE_FEATURES_H_
+#define LIBTEXTCLASSIFIER_LANG_ID_LIGHT_SENTENCE_FEATURES_H_
 
 #include "common/feature-extractor.h"
 #include "lang_id/light-sentence.h"
@@ -31,7 +31,11 @@
 typedef FeatureExtractor<LightSentence> LightSentenceExtractor;
 
 }  // namespace lang_id
+
+// Should be used in namespace libtextclassifier::nlp_core.
+TC_DECLARE_CLASS_REGISTRY_NAME(lang_id::LightSentenceFeature);
+
 }  // namespace nlp_core
 }  // namespace libtextclassifier
 
-#endif  // LIBTEXTCLASSIFIER_LANG_ID_LIGH_SENTENCE_FEATURES_H_
+#endif  // LIBTEXTCLASSIFIER_LANG_ID_LIGHT_SENTENCE_FEATURES_H_
diff --git a/lang_id/light-sentence.h b/lang_id/light-sentence.h
index 98aca3d..e8451be 100644
--- a/lang_id/light-sentence.h
+++ b/lang_id/light-sentence.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef LIBTEXTCLASSIFIER_LANG_ID_LIGH_SENTENCE_H_
-#define LIBTEXTCLASSIFIER_LANG_ID_LIGH_SENTENCE_H_
+#ifndef LIBTEXTCLASSIFIER_LANG_ID_LIGHT_SENTENCE_H_
+#define LIBTEXTCLASSIFIER_LANG_ID_LIGHT_SENTENCE_H_
 
 #include <string>
 #include <vector>
@@ -63,4 +63,4 @@
 }  // namespace nlp_core
 }  // namespace libtextclassifier
 
-#endif  // LIBTEXTCLASSIFIER_LANG_ID_LIGH_SENTENCE_H_
+#endif  // LIBTEXTCLASSIFIER_LANG_ID_LIGHT_SENTENCE_H_
diff --git a/smartselect/feature-processor.cc b/smartselect/feature-processor.cc
index 9e357bd..90ed241 100644
--- a/smartselect/feature-processor.cc
+++ b/smartselect/feature-processor.cc
@@ -163,6 +163,31 @@
   }
 }
 
+std::vector<Token> FindTokensInSelection(
+    const std::vector<Token>& selectable_tokens,
+    const SelectionWithContext& selection_with_context) {
+  std::vector<Token> tokens_in_selection;
+  for (const Token& token : selectable_tokens) {
+    const bool selection_start_in_token =
+        token.start <= selection_with_context.selection_start &&
+        token.end > selection_with_context.selection_start;
+
+    const bool token_contained_in_selection =
+        token.start >= selection_with_context.selection_start &&
+        token.end < selection_with_context.selection_end;
+
+    const bool selection_end_in_token =
+        token.start < selection_with_context.selection_end &&
+        token.end >= selection_with_context.selection_end;
+
+    if (selection_start_in_token || token_contained_in_selection ||
+        selection_end_in_token) {
+      tokens_in_selection.push_back(token);
+    }
+  }
+  return tokens_in_selection;
+}
+
 }  // namespace internal
 
 const char* const FeatureProcessor::kFeatureTypeName = "chargram_continuous";
@@ -372,36 +397,11 @@
   }
 }
 
-std::vector<Token> FeatureProcessor::FindTokensInSelection(
-    const std::vector<Token>& selectable_tokens,
-    const SelectionWithContext& selection_with_context) const {
-  std::vector<Token> tokens_in_selection;
-  for (const Token& token : selectable_tokens) {
-    const bool selection_start_in_token =
-        token.start <= selection_with_context.selection_start &&
-        token.end > selection_with_context.selection_start;
-
-    const bool token_contained_in_selection =
-        token.start >= selection_with_context.selection_start &&
-        token.end < selection_with_context.selection_end;
-
-    const bool selection_end_in_token =
-        token.start < selection_with_context.selection_end &&
-        token.end >= selection_with_context.selection_end;
-
-    if (selection_start_in_token || token_contained_in_selection ||
-        selection_end_in_token) {
-      tokens_in_selection.push_back(token);
-    }
-  }
-  return tokens_in_selection;
-}
-
 CodepointSpan FeatureProcessor::ClickRandomTokenInSelection(
     const SelectionWithContext& selection_with_context) const {
   const std::vector<Token> tokens = Tokenize(selection_with_context.context);
   const std::vector<Token> tokens_in_selection =
-      FindTokensInSelection(tokens, selection_with_context);
+      internal::FindTokensInSelection(tokens, selection_with_context);
 
   if (!tokens_in_selection.empty()) {
     std::uniform_int_distribution<> selection_token_draw(
diff --git a/smartselect/feature-processor.h b/smartselect/feature-processor.h
index 311be3e..619ea4d 100644
--- a/smartselect/feature-processor.h
+++ b/smartselect/feature-processor.h
@@ -61,6 +61,12 @@
 int CenterTokenFromMiddleOfSelection(
     CodepointSpan span, const std::vector<Token>& selectable_tokens);
 
+// Finds tokens that are part of the selection.
+// NOTE: Will select all tokens that somehow overlap with the selection.
+std::vector<Token> FindTokensInSelection(
+    const std::vector<Token>& selectable_tokens,
+    const SelectionWithContext& selection_with_context);
+
 }  // namespace internal
 
 TokenSpan CodepointSpanToTokenSpan(const std::vector<Token>& selectable_tokens,
@@ -186,12 +192,6 @@
   // Converts a token span to the corresponding label.
   int TokenSpanToLabel(const std::pair<TokenIndex, TokenIndex>& span) const;
 
-  // Finds tokens that are part of the selection.
-  // NOTE: Will select all tokens that somehow overlap with the selection.
-  std::vector<Token> FindTokensInSelection(
-      const std::vector<Token>& selectable_tokens,
-      const SelectionWithContext& selection_with_context) const;
-
   // Finds the center token index in tokens vector, using the method defined
   // in options_.
   int FindCenterToken(CodepointSpan span,
diff --git a/smartselect/types.h b/smartselect/types.h
index d7c1b82..7367ed0 100644
--- a/smartselect/types.h
+++ b/smartselect/types.h
@@ -126,4 +126,4 @@
 
 }  // namespace libtextclassifier
 
-#endif  // LIBTEXTCLASSIFIER_TYPES_H_
+#endif  // LIBTEXTCLASSIFIER_SMARTSELECT_TYPES_H_
diff --git a/tests/feature-processor_test.cc b/tests/feature-processor_test.cc
index 652db84..27cac6a 100644
--- a/tests/feature-processor_test.cc
+++ b/tests/feature-processor_test.cc
@@ -270,12 +270,6 @@
   EXPECT_EQ(19, features.size());
 }
 
-class TestingFeatureProcessor : public FeatureProcessor {
- public:
-  using FeatureProcessor::FeatureProcessor;
-  using FeatureProcessor::FindTokensInSelection;
-};
-
 TEST(FeatureProcessorTest, FindTokensInSelectionSingleCharacter) {
   FeatureProcessorOptions options;
   options.set_num_buckets(10);
@@ -288,7 +282,7 @@
   config->set_start(32);
   config->set_end(33);
   config->set_role(TokenizationCodepointRange::WHITESPACE_SEPARATOR);
-  TestingFeatureProcessor feature_processor(options);
+  FeatureProcessor feature_processor(options);
 
   SelectionWithContext selection_with_context;
   selection_with_context.context = "1 2 3 c o n t e x t X c o n t e x t 1 2 3";
@@ -297,7 +291,7 @@
   selection_with_context.selection_start = 20;
   selection_with_context.selection_end = 21;
   // clang-format off
-  EXPECT_THAT(feature_processor.FindTokensInSelection(
+  EXPECT_THAT(internal::FindTokensInSelection(
                   feature_processor.Tokenize(selection_with_context.context),
                   selection_with_context),
               ElementsAreArray({Token("X", 20, 21, false)}));
@@ -316,7 +310,7 @@
   config->set_start(32);
   config->set_end(33);
   config->set_role(TokenizationCodepointRange::WHITESPACE_SEPARATOR);
-  TestingFeatureProcessor feature_processor(options);
+  FeatureProcessor feature_processor(options);
 
   SelectionWithContext selection_with_context;
   selection_with_context.context = "I live at 350 Third Street, today.";
@@ -332,7 +326,7 @@
   // Selection: I live at {350 Third Str}eet, today.
   selection_with_context.selection_start = 10;
   selection_with_context.selection_end = 23;
-  EXPECT_THAT(feature_processor.FindTokensInSelection(
+  EXPECT_THAT(internal::FindTokensInSelection(
                   feature_processor.Tokenize(selection_with_context.context),
                   selection_with_context),
               ElementsAreArray(expected_selection));
@@ -340,7 +334,7 @@
   // Selection: I live at {350 Third Street,} today.
   selection_with_context.selection_start = 10;
   selection_with_context.selection_end = 27;
-  EXPECT_THAT(feature_processor.FindTokensInSelection(
+  EXPECT_THAT(internal::FindTokensInSelection(
                   feature_processor.Tokenize(selection_with_context.context),
                   selection_with_context),
               ElementsAreArray(expected_selection));
@@ -348,7 +342,7 @@
   // Selection: I live at {350 Third Street, }today.
   selection_with_context.selection_start = 10;
   selection_with_context.selection_end = 28;
-  EXPECT_THAT(feature_processor.FindTokensInSelection(
+  EXPECT_THAT(internal::FindTokensInSelection(
                   feature_processor.Tokenize(selection_with_context.context),
                   selection_with_context),
               ElementsAreArray(expected_selection));
@@ -356,7 +350,7 @@
   // Selection: I live at {350 Third S}treet, today.
   selection_with_context.selection_start = 10;
   selection_with_context.selection_end = 21;
-  EXPECT_THAT(feature_processor.FindTokensInSelection(
+  EXPECT_THAT(internal::FindTokensInSelection(
                   feature_processor.Tokenize(selection_with_context.context),
                   selection_with_context),
               ElementsAreArray(expected_selection));
@@ -366,7 +360,7 @@
   // Selection: I live at {350 Third} Street, today.
   selection_with_context.selection_start = 10;
   selection_with_context.selection_end = 19;
-  EXPECT_THAT(feature_processor.FindTokensInSelection(
+  EXPECT_THAT(internal::FindTokensInSelection(
                   feature_processor.Tokenize(selection_with_context.context),
                   selection_with_context),
               ElementsAreArray({
@@ -380,7 +374,7 @@
   selection_with_context.selection_start = 10;
   selection_with_context.selection_end = 29;
   EXPECT_THAT(
-      feature_processor.FindTokensInSelection(
+      internal::FindTokensInSelection(
           feature_processor.Tokenize(selection_with_context.context),
           selection_with_context),
       ElementsAreArray({
@@ -453,6 +447,46 @@
                  Token("Token3", 14, 20, false), Token("Token4", 21, 27, false),
                  Token("Token5", 28, 34, false)});
   EXPECT_EQ(token_index, 4);
+
+  // Some invalid ones.
+  token_index = internal::CenterTokenFromMiddleOfSelection({7, 27}, {});
+  EXPECT_EQ(token_index, -1);
+}
+
+TEST(FeatureProcessorTest, GetFeaturesForSharing) {
+  FeatureProcessorOptions options;
+  options.set_num_buckets(10);
+  options.set_context_size(9);
+  options.set_max_selection_span(7);
+  options.add_chargram_orders(1);
+  options.set_tokenize_on_space(true);
+  options.set_center_token_selection_method(
+      FeatureProcessorOptions::CENTER_TOKEN_MIDDLE_OF_SELECTION);
+  options.set_only_use_line_with_click(true);
+  options.set_split_tokens_on_selection_boundaries(true);
+  options.set_extract_selection_mask_feature(true);
+  TokenizationCodepointRange* config =
+      options.add_tokenization_codepoint_config();
+  config->set_start(32);
+  config->set_end(33);
+  config->set_role(TokenizationCodepointRange::WHITESPACE_SEPARATOR);
+  config = options.add_tokenization_codepoint_config();
+  config->set_start(10);
+  config->set_end(11);
+  config->set_role(TokenizationCodepointRange::WHITESPACE_SEPARATOR);
+  FeatureProcessor feature_processor(options);
+
+  std::vector<std::vector<std::pair<int, float>>> features;
+  std::vector<float> extra_features;
+  std::vector<CodepointSpan> selection_label_spans;
+  int selection_label;
+  CodepointSpan selection_codepoint_label;
+  int classification_label;
+  EXPECT_TRUE(feature_processor.GetFeaturesAndLabels(
+      "line 1\nline2\nsome entity\n line 4", {13, 24}, {13, 24}, "", &features,
+      &extra_features, &selection_label_spans, &selection_label,
+      &selection_codepoint_label, &classification_label));
+  EXPECT_EQ(19, features.size());
 }
 
 }  // namespace
diff --git a/tests/functions.cc b/tests/functions.cc
new file mode 100644
index 0000000..8ea5a8d
--- /dev/null
+++ b/tests/functions.cc
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "tests/functions.h"
+
+#include "common/registry.h"
+
+namespace libtextclassifier {
+namespace nlp_core {
+
+TC_DEFINE_CLASS_REGISTRY_NAME("function", functions::Function);
+
+TC_DEFINE_CLASS_REGISTRY_NAME("int-function", functions::IntFunction);
+
+}  // namespace nlp_core
+}  // namespace libtextclassifier
diff --git a/tests/functions.h b/tests/functions.h
new file mode 100644
index 0000000..b96fe2d
--- /dev/null
+++ b/tests/functions.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef LIBTEXTCLASSIFIER_TESTS_FUNCTIONS_H_
+#define LIBTEXTCLASSIFIER_TESTS_FUNCTIONS_H_
+
+#include <math.h>
+
+#include "common/registry.h"
+
+namespace libtextclassifier {
+namespace nlp_core {
+namespace functions {
+// Abstract double -> double function.
+class Function : public RegisterableClass<Function> {
+ public:
+  virtual ~Function() {}
+  virtual double Evaluate(double x) = 0;
+};
+
+class Cos : public Function {
+ public:
+  double Evaluate(double x) override { return cos(x); }
+  TC_DEFINE_REGISTRATION_METHOD("cos", Cos);
+};
+
+class Exp : public Function {
+ public:
+  double Evaluate(double x) override { return exp(x); }
+  TC_DEFINE_REGISTRATION_METHOD("exp", Exp);
+};
+
+// Abstract int -> int function.
+class IntFunction : public RegisterableClass<IntFunction> {
+ public:
+  virtual ~IntFunction() {}
+  virtual int Evaluate(int k) = 0;
+};
+
+class Inc : public IntFunction {
+ public:
+  int Evaluate(int k) override { return k + 1; }
+  TC_DEFINE_REGISTRATION_METHOD("inc", Inc);
+};
+
+class Dec : public IntFunction {
+ public:
+  int Evaluate(int k) override { return k + 1; }
+  TC_DEFINE_REGISTRATION_METHOD("dec", Dec);
+};
+}  // namespace functions
+
+// Should be inside namespace libtextclassifier::nlp_core.
+TC_DECLARE_CLASS_REGISTRY_NAME(functions::Function);
+TC_DECLARE_CLASS_REGISTRY_NAME(functions::IntFunction);
+
+}  // namespace nlp_core
+}  // namespace libtextclassifier
+
+#endif  // LIBTEXTCLASSIFIER_TESTS_FUNCTIONS_H_
diff --git a/tests/registry_test.cc b/tests/registry_test.cc
new file mode 100644
index 0000000..7de4163
--- /dev/null
+++ b/tests/registry_test.cc
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <memory>
+
+#include "tests/functions.h"
+#include "gtest/gtest.h"
+
+namespace libtextclassifier {
+namespace nlp_core {
+namespace functions {
+
+TEST(RegistryTest, InstantiateFunctionsByName) {
+  // First, we need to register the functions we are interested in:
+  Exp::RegisterClass();
+  Inc::RegisterClass();
+  Cos::RegisterClass();
+
+  // RegisterClass methods can be called in any order, even multiple times :)
+  Cos::RegisterClass();
+  Inc::RegisterClass();
+  Inc::RegisterClass();
+  Cos::RegisterClass();
+  Inc::RegisterClass();
+
+  // NOTE: we intentionally do not register Dec.  Attempts to create an instance
+  // of that function by name should fail.
+
+  // Instantiate a few functions and check that the created functions produce
+  // the expected results for a few sample values.
+  std::unique_ptr<Function> f1(Function::Create("cos"));
+  ASSERT_NE(f1, nullptr);
+  std::unique_ptr<Function> f2(Function::Create("exp"));
+  ASSERT_NE(f2, nullptr);
+  EXPECT_NEAR(f1->Evaluate(-3), -0.9899, 0.0001);
+  EXPECT_NEAR(f2->Evaluate(2.3), 9.9741, 0.0001);
+
+  std::unique_ptr<IntFunction> f3(IntFunction::Create("inc"));
+  ASSERT_NE(f3, nullptr);
+  EXPECT_EQ(f3->Evaluate(7), 8);
+
+  // Instantiating unknown functions should return nullptr, but not crash
+  // anything.
+  EXPECT_EQ(Function::Create("mambo"), nullptr);
+
+  // Functions that are defined in the code, but are not registered are unknown.
+  EXPECT_EQ(IntFunction::Create("dec"), nullptr);
+
+  // Function and IntFunction use different registries.
+  EXPECT_EQ(IntFunction::Create("exp"), nullptr);
+}
+
+}  // namespace functions
+}  // namespace nlp_core
+}  // namespace libtextclassifier
diff --git a/tests/text-classification-model_test.cc b/tests/text-classification-model_test.cc
index 2e2e841..20351c6 100644
--- a/tests/text-classification-model_test.cc
+++ b/tests/text-classification-model_test.cc
@@ -257,6 +257,12 @@
   EXPECT_EQ("other", FindBestResult(model->ClassifyText("asdf", {0, 4})));
   EXPECT_EQ("<INVALID RESULTS>",
             FindBestResult(model->ClassifyText("asdf", {0, 0})));
+
+  // Junk.
+  EXPECT_EQ("<INVALID RESULTS>",
+            FindBestResult(model->ClassifyText("", {0, 0})));
+  EXPECT_EQ("<INVALID RESULTS>", FindBestResult(model->ClassifyText(
+                                     "a\n\n\n\nx x x\n\n\n\n\n\n", {1, 5})));
 }
 
 }  // namespace