Create full test for ld.config.txt content

There are some module level unit tests and backward compatibility tests,
but still there is no test that verifies ld.config.txt content is
valid in terms of syntax and also context. To test both
features in basic level, this change adds full content test which parses
content and checks if every context (such as path exists and links
between namespace is valid) is able to be used by linker.

Bug: 139639353
Test: m -j && atest passed
Change-Id: I58255ccc6602f6bcb86b897fc89f5581ccc18f08
diff --git a/Android.bp b/Android.bp
index 92d84f0..bcd5d47 100644
--- a/Android.bp
+++ b/Android.bp
@@ -126,4 +126,19 @@
     data: [
         "generator/tests/data/*.txt",
     ],
+}
+
+cc_test {
+    name: "linkerconfig_contents_fulltest",
+    defaults: [ "linkerconfig_test_defaults" ],
+    local_include_dirs: [
+        "contents/tests/configuration/include",
+    ],
+    srcs: [
+        "contents/tests/configuration/*_test.cc",
+    ],
+    static_libs: [
+        "linkerconfig_modules",
+        "linkerconfig_contents",
+    ],
 }
\ No newline at end of file
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 4b4c9eb..82cfd3b 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -7,6 +7,9 @@
       "name" : "linkerconfig_backward_compatibility_test"
     },
     {
+      "name" : "linkerconfig_contents_fulltest"
+    },
+    {
       "name" : "linkerconfig_generator_unittest"
     }
   ]
diff --git a/contents/namespace/vndk.cc b/contents/namespace/vndk.cc
index ed3bd78..d691b08 100644
--- a/contents/namespace/vndk.cc
+++ b/contents/namespace/vndk.cc
@@ -64,7 +64,7 @@
 
     if (android::linkerconfig::modules::IsVndkInSystemNamespace()) {
       ns.CreateLink("vndk_in_system")
-          .AddSharedLib("@{VNDK_USING_CORE_VARIANT_LIBRARIES");
+          .AddSharedLib("@{VNDK_USING_CORE_VARIANT_LIBRARIES}");
     }
   }
 
diff --git a/contents/tests/configuration/baseconfig_test.cc b/contents/tests/configuration/baseconfig_test.cc
new file mode 100644
index 0000000..231b087
--- /dev/null
+++ b/contents/tests/configuration/baseconfig_test.cc
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 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 "linkerconfig/baseconfig.h"
+#include "configurationtest.h"
+#include "linkerconfig/configwriter.h"
+#include "mockenv.h"
+
+TEST(linkerconfig_configuration_fulltest, baseconfig_test) {
+  MockGenericVariables();
+  auto base_config = android::linkerconfig::contents::CreateBaseConfiguration();
+  android::linkerconfig::modules::ConfigWriter config_writer;
+
+  base_config.WriteConfig(config_writer);
+
+  VerifyConfiguration(config_writer.ToString());
+}
+
+TEST(linkerconfig_configuration_fulltest, baseconfig_vndk_27_test) {
+  MockGenericVariables();
+  MockVndkVersion("27");
+  auto base_config = android::linkerconfig::contents::CreateBaseConfiguration();
+  android::linkerconfig::modules::ConfigWriter config_writer;
+
+  base_config.WriteConfig(config_writer);
+
+  VerifyConfiguration(config_writer.ToString());
+}
\ No newline at end of file
diff --git a/contents/tests/configuration/include/configurationtest.h b/contents/tests/configuration/include/configurationtest.h
new file mode 100644
index 0000000..0846037
--- /dev/null
+++ b/contents/tests/configuration/include/configurationtest.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+#pragma once
+
+#include <unordered_set>
+
+#include "gtest/gtest.h"
+#include "linkerconfigparser.h"
+#include "modules.h"
+
+namespace {
+inline void TraverseLink(const Namespace& ns, std::unordered_set<std::string>& visible_ns) {
+  if (visible_ns.count(ns.name) != 0) {
+    return;
+  }
+
+  visible_ns.insert(ns.name);
+
+  for (auto& [_, link] : ns.links) {
+    TraverseLink(*link.to, visible_ns);
+  }
+}
+
+inline void ValidateAllNamespacesAreVisible(const Section& section) {
+  std::unordered_set<std::string> visible_ns;
+  for (auto& [_, ns] : section.namespaces) {
+    if (ns.name == "default" || ns.is_visible) {
+      TraverseLink(ns, visible_ns);
+    }
+  }
+
+  for (auto& [_, ns] : section.namespaces) {
+    EXPECT_EQ(1u, visible_ns.count(ns.name))
+        << "Namespace " << ns.name << " is not visible from section " << section.name;
+  }
+}
+
+inline void ValidateNamespace(const Namespace& target_namespace, const Section& parent_section) {
+  EXPECT_FALSE(target_namespace.name.empty()) << "Namespace name should not be empty";
+  EXPECT_FALSE(target_namespace.search_path.empty() && target_namespace.permitted_path.empty())
+      << "Search path or permitted path should be defined in namespace " << target_namespace.name
+      << " from section " << parent_section.name;
+}
+
+inline void ValidateSection(const Section& section) {
+  EXPECT_FALSE(section.name.empty()) << "Section name should not be empty";
+  EXPECT_NE(0u, section.namespaces.size())
+      << "Section " << section.name << " should contain at least one namespace";
+  EXPECT_NE(0u, section.dirs.size())
+      << "Section " << section.name << "does not contain any directory as executable path";
+  EXPECT_TRUE(MapContainsKey(section.namespaces, std::string("default")))
+      << "Section " << section.name << " should contain namespace named 'default'";
+
+  for (auto& [_, target_namespace] : section.namespaces) {
+    ValidateNamespace(target_namespace, section);
+  }
+
+  ValidateAllNamespacesAreVisible(section);
+}
+
+inline void ValidateConfiguration(const Configuration& conf) {
+  EXPECT_NE(0u, conf.sections.size());
+  for (auto& [_, section] : conf.sections) {
+    ValidateSection(section);
+  }
+}
+}  // namespace
+
+inline void VerifyConfiguration(const std::string& configuration_str) {
+  Configuration conf;
+  ParseConfiguration(configuration_str, conf);
+  ValidateConfiguration(conf);
+}
\ No newline at end of file
diff --git a/contents/tests/configuration/include/linkerconfigparser.h b/contents/tests/configuration/include/linkerconfigparser.h
new file mode 100644
index 0000000..4cd803e
--- /dev/null
+++ b/contents/tests/configuration/include/linkerconfigparser.h
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+#pragma once
+
+#include <cstring>
+#include <regex>
+
+#include "gtest/gtest.h"
+#include "modules.h"
+
+namespace {
+constexpr const char* kSectionNameRegex = "\\[\\s*(\\w+)\\s*\\]";
+constexpr const char* kDirRegex = "dir\\.(\\w+)\\s*=\\s*([\\w/]+)";
+constexpr const char* kNamespaceBaseRegex =
+    "namespace\\.(\\w+)\\.([^\\s=]+)\\s*(=|\\+=)\\s*([^\\s]+)";
+constexpr const char* kAdditionalNamespacesRegex =
+    "additional\\.namespaces\\s*=\\s*((?:[\\w]+)(?:,[\\w]+)*)";
+
+// Functions to parse configuration string and verify syntax
+
+inline void ParseDirPath(const std::string& line, Configuration& conf) {
+  static std::regex dir_regex(kDirRegex);
+  std::smatch match;
+
+  ASSERT_TRUE(std::regex_match(line, match, dir_regex));
+  ASSERT_EQ(3u, match.size());
+  std::string section_name = match[1];
+  std::string dir_path = match[2];
+
+  if (!MapContainsKey(conf.sections, section_name)) {
+    conf.sections[section_name].name = section_name;
+    conf.sections[section_name].namespaces["default"].name = "default";
+  }
+
+  conf.sections[section_name].dirs.push_back(dir_path);
+}
+
+inline void ParseAdditionalNamespaces(const std::smatch& match,
+                                      Section& current_section) {
+  // additional.namespace = a,b,c,e,d
+  ASSERT_EQ(2u, match.size());
+  std::stringstream namespaces(match[1]);
+  for (std::string namespace_name;
+       std::getline(namespaces, namespace_name, ',');) {
+    EXPECT_FALSE(MapContainsKey(current_section.namespaces, namespace_name));
+    Namespace new_namespace;
+    new_namespace.name = namespace_name;
+    current_section.namespaces[namespace_name] = new_namespace;
+  }
+}
+
+inline void ParseNamespacePath(const std::vector<std::string>& property_descs,
+                               const bool is_additional, const std::string& path,
+                               Namespace& current_namespace,
+                               const std::string& line) {
+  // namespace.test.(asan.)search|permitted.path =|+= /path/to/${LIB}/dir
+  ASSERT_EQ(property_descs[0] == "asan" ? 3u : 2u, property_descs.size());
+
+  std::vector<std::string>* target_path = nullptr;
+  if (property_descs[0] == "search") {
+    target_path = &current_namespace.search_path;
+  } else if (property_descs[0] == "permitted") {
+    target_path = &current_namespace.permitted_path;
+  } else if (property_descs[0] == "asan" && property_descs[1] == "search") {
+    target_path = &current_namespace.asan_search_path;
+  } else if (property_descs[0] == "asan" && property_descs[1] == "permitted") {
+    target_path = &current_namespace.asan_permitted_path;
+  }
+
+  ASSERT_NE(nullptr, target_path);
+  EXPECT_EQ(is_additional, target_path->size() != 0)
+      << "Path should be marked as = if and only if it is mentioned first : "
+      << line;
+
+  target_path->push_back(path);
+}
+
+inline void ParseLinkList(const std::vector<std::string>& property_descs,
+                          const std::string& target_namespaces,
+                          Namespace& current_namespace,
+                          Section& current_section, const std::string& line) {
+  // namespace.test.links = a,b,c,d,e
+  EXPECT_EQ(1u, property_descs.size());
+  std::stringstream namespaces(target_namespaces);
+  for (std::string namespace_to; std::getline(namespaces, namespace_to, ',');) {
+    EXPECT_FALSE(MapContainsKey(current_namespace.links, namespace_to))
+        << "Link to " << namespace_to << " is already defined : " << line;
+    EXPECT_TRUE(MapContainsKey(current_section.namespaces, namespace_to))
+        << "Target namespace is not defined in section : " << line;
+
+    current_namespace.links[namespace_to].from = &current_namespace;
+    current_namespace.links[namespace_to].to =
+        &current_section.namespaces[namespace_to];
+    current_namespace.links[namespace_to].allow_all_shared = false;
+  }
+}
+
+inline void ParseLink(const std::vector<std::string>& property_descs,
+                      const bool is_additional, const std::string& value,
+                      Namespace& current_namespace, Section& current_section,
+                      const std::string& line) {
+  // namespace.from.link.to.shared_libs = a.so
+  // namespace.from.link.to.allow_all_shared_libs = true
+  ASSERT_EQ(3u, property_descs.size());
+  ASSERT_TRUE(property_descs[2] == "shared_libs" ||
+              property_descs[2] == "allow_all_shared_libs");
+  std::string namespace_to = property_descs[1];
+
+  ASSERT_TRUE(MapContainsKey(current_section.namespaces, namespace_to))
+      << "To namespace does not exist in section " << current_section.name
+      << " : " << line;
+
+  if (property_descs[2] == "shared_libs") {
+    EXPECT_EQ(is_additional,
+              current_namespace.links[namespace_to].shared_libs.size() != 0)
+        << "Link should be defined with = if and only if it is first link "
+           "between two namespaces : "
+        << line;
+
+    current_namespace.links[namespace_to].shared_libs.push_back(value);
+  } else {
+    EXPECT_EQ("true", value);
+    current_namespace.links[namespace_to].allow_all_shared = true;
+  }
+}
+
+inline void ParseNamespaceCommand(const std::string& namespace_name,
+                                  const std::string& property_desc,
+                                  const bool is_additional_property,
+                                  const std::string& value,
+                                  Section& current_section,
+                                  const std::string& line) {
+  ASSERT_TRUE(MapContainsKey(current_section.namespaces, namespace_name))
+      << "Namespace " << namespace_name << " does not exist in section "
+      << current_section.name << " : " << line;
+  Namespace& current_namespace = current_section.namespaces[namespace_name];
+
+  std::vector<std::string> property_descs;
+  std::stringstream property_desc_stream(property_desc);
+  for (std::string property;
+       std::getline(property_desc_stream, property, '.');) {
+    property_descs.push_back(property);
+  }
+
+  ASSERT_TRUE(property_descs.size() > 0)
+      << "There should be at least one property description after namespace."
+      << namespace_name << " : " << line;
+
+  if (property_descs[0].compare("isolated") == 0) {
+    // namespace.test.isolated = true
+    EXPECT_EQ(1u, property_descs.size());
+    EXPECT_TRUE(value == "true" || value == "false");
+    current_namespace.is_isolated = value == "true";
+  } else if (property_descs[0].compare("visible") == 0) {
+    // namespace.test.visible = true
+    EXPECT_EQ(1u, property_descs.size());
+    EXPECT_TRUE(value == "true" || value == "false");
+    current_namespace.is_visible = value == "true";
+  } else if (property_descs[property_descs.size() - 1] == "paths") {
+    // namespace.test.search.path += /system/lib
+    ParseNamespacePath(
+        property_descs, is_additional_property, value, current_namespace, line);
+  } else if (property_descs[0] == "links") {
+    // namespace.test.links = a,b,c
+    ParseLinkList(
+        property_descs, value, current_namespace, current_section, line);
+  } else if (property_descs[0] == "link") {
+    // namespace.test.link.a = libc.so
+    ParseLink(property_descs,
+              is_additional_property,
+              value,
+              current_namespace,
+              current_section,
+              line);
+  } else if (property_descs[0] == "whitelisted") {
+    EXPECT_EQ(1u, property_descs.size());
+    current_namespace.whitelisted.push_back(value);
+  } else {
+    EXPECT_TRUE(false) << "Failed to parse line : " << line;
+  }
+}
+}  // namespace
+
+inline void ParseConfiguration(const std::string& configuration_str,
+                               Configuration& conf) {
+  Section* current_section = nullptr;
+
+  static std::regex section_name_regex(kSectionNameRegex);
+  static std::regex additional_namespaces_regex(kAdditionalNamespacesRegex);
+  static std::regex namespace_base_regex(kNamespaceBaseRegex);
+
+  std::smatch match;
+
+  std::stringstream configuration_stream(configuration_str);
+
+  for (std::string line; std::getline(configuration_stream, line);) {
+    // Skip empty line
+    if (line.empty()) {
+      continue;
+    }
+
+    if (std::regex_match(line, match, section_name_regex)) {
+      // [section_name]
+      ASSERT_EQ(2u, match.size());
+      std::string section_name = match[1];
+      ASSERT_TRUE(MapContainsKey(conf.sections, section_name));
+      current_section = &conf.sections[section_name];
+
+      continue;
+    }
+
+    if (current_section == nullptr) {
+      ParseDirPath(line, conf);
+    } else {
+      if (std::regex_match(line, match, additional_namespaces_regex)) {
+        ParseAdditionalNamespaces(match, *current_section);
+      } else {
+        EXPECT_TRUE(std::regex_match(line, match, namespace_base_regex));
+        ASSERT_EQ(5u, match.size());
+        std::string namespace_name = match[1];
+        std::string property_desc = match[2];
+        bool is_additional_property = match[3] == "+=";
+        std::string content = match[4];
+        ParseNamespaceCommand(namespace_name,
+                              property_desc,
+                              is_additional_property,
+                              content,
+                              *current_section,
+                              line);
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/contents/tests/configuration/include/mockenv.h b/contents/tests/configuration/include/mockenv.h
new file mode 100644
index 0000000..f7e37f7
--- /dev/null
+++ b/contents/tests/configuration/include/mockenv.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+#pragma once
+
+#include "linkerconfig/variables.h"
+
+inline void MockGenericVariables() {
+  android::linkerconfig::modules::Variables::AddValue("VNDK_VER", "99");
+  android::linkerconfig::modules::Variables::AddValue("PRODUCT", "product");
+  android::linkerconfig::modules::Variables::AddValue("SYSTEM_EXT",
+                                                      "system_ext");
+  android::linkerconfig::modules::Variables::AddValue("LLNDK_LIBRARIES",
+                                                      "llndk_libraries");
+  android::linkerconfig::modules::Variables::AddValue(
+      "SANITIZER_RUNTIME_LIBRARIES", "sanitizer_runtime_libraries");
+  android::linkerconfig::modules::Variables::AddValue(
+      "PRIVATE_LLNDK_LIBRARIES", "private_llndk_libraries");
+  android::linkerconfig::modules::Variables::AddValue(
+      "VNDK_SAMEPROCESS_LIBRARIES", "vndk_sameprocess_libraries");
+  android::linkerconfig::modules::Variables::AddValue("VNDK_CORE_LIBRARIES",
+                                                      "vndk_core_libraries");
+  android::linkerconfig::modules::Variables::AddValue(
+      "VNDK_USING_CORE_VARIANT_LIBRARIES", "vndk_using_core_variant_libraries");
+}
+
+inline void MockVndkVersion(std::string vndk_version) {
+  android::linkerconfig::modules::Variables::AddValue("VNDK_VER", vndk_version);
+}
\ No newline at end of file
diff --git a/contents/tests/configuration/include/modules.h b/contents/tests/configuration/include/modules.h
new file mode 100644
index 0000000..998aad0
--- /dev/null
+++ b/contents/tests/configuration/include/modules.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+#pragma once
+
+#include <map>
+#include <string>
+#include <vector>
+
+// Define basic data structure for linker configuration
+struct Namespace;
+
+struct Link {
+  Namespace *from, *to;
+  bool allow_all_shared;
+  std::vector<std::string> shared_libs;
+};
+
+struct Namespace {
+  std::string name;
+  bool is_isolated;
+  bool is_visible;
+  std::vector<std::string> search_path;
+  std::vector<std::string> permitted_path;
+  std::vector<std::string> asan_search_path;
+  std::vector<std::string> asan_permitted_path;
+  std::map<std::string, Link> links;
+  std::vector<std::string> whitelisted;
+};
+
+struct Section {
+  std::string name;
+  std::vector<std::string> dirs;
+  std::map<std::string, Namespace> namespaces;
+};
+
+struct Configuration {
+  std::map<std::string, Section> sections;
+};
+
+template <class K, class V>
+bool MapContainsKey(const std::map<K, V>& map, K key) {
+  return map.find(key) != map.end();
+}
\ No newline at end of file
diff --git a/contents/tests/configuration/legacy_test.cc b/contents/tests/configuration/legacy_test.cc
new file mode 100644
index 0000000..721a8ad
--- /dev/null
+++ b/contents/tests/configuration/legacy_test.cc
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 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 "linkerconfig/legacy.h"
+#include "configurationtest.h"
+#include "linkerconfig/configwriter.h"
+#include "mockenv.h"
+
+TEST(linkerconfig_configuration_fulltest, legacy_test) {
+  MockGenericVariables();
+  auto legacy_config =
+      android::linkerconfig::contents::CreateLegacyConfiguration();
+  android::linkerconfig::modules::ConfigWriter config_writer;
+
+  legacy_config.WriteConfig(config_writer);
+
+  VerifyConfiguration(config_writer.ToString());
+}
\ No newline at end of file