Move On device test to AOSP

Current linkerconfig GTS test is located within internal repository, and
this blocks AOSP to test on device full test. As there is no concern of
moving this test into open source repository, this change relocates
existing GTS linkerconfig test to AOSP.

Bug: 302087260
Test: GtsLinkerConfigTestCases build and run succeeded from AOSP build
Change-Id: I29978bc4bcf7aa83e6546262e1cb754182043e62
diff --git a/devicetest/Android.bp b/devicetest/Android.bp
new file mode 100644
index 0000000..233b5cb
--- /dev/null
+++ b/devicetest/Android.bp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 Google LLC.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package {
+    default_applicable_licenses: [
+        "Android-Apache-2.0",
+    ],
+}
+
+java_test_host {
+    name: "GtsLinkerConfigTestCases",
+    libs: [
+        "tradefed",
+    ],
+    static_libs: [
+        "hamcrest-library",
+        "compatibility-host-util",
+    ],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a gts test artifact
+    test_suites: [
+        "gts",
+        "general-tests",
+        "mts-mainline-infra",
+    ],
+}
diff --git a/devicetest/AndroidTest.xml b/devicetest/AndroidTest.xml
new file mode 100644
index 0000000..44f325d
--- /dev/null
+++ b/devicetest/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 Google LLC.
+
+     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.
+-->
+<configuration description="Config for GTS Linkerconfig test cases">
+    <option name="test-suite-tag" value="gts" />
+    <option name="config-descriptor:metadata" key="component" value="gms" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
+    <test class="com.android.tradefed.testtype.HostTest" >
+        <option name="jar" value="GtsLinkerConfigTestCases.jar" />
+    </test>
+
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController"/>
+</configuration>
diff --git a/devicetest/src/android/linkerconfig/gts/LinkerConfigTest.java b/devicetest/src/android/linkerconfig/gts/LinkerConfigTest.java
new file mode 100644
index 0000000..e24ae18
--- /dev/null
+++ b/devicetest/src/android/linkerconfig/gts/LinkerConfigTest.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2020 Google LLC.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.linkerconfig.gts;
+
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.linkerconfig.gts.utils.LibraryListLoader;
+import android.linkerconfig.gts.utils.LinkerConfigParser;
+import android.linkerconfig.gts.utils.elements.Configuration;
+import android.linkerconfig.gts.utils.elements.Namespace;
+import android.linkerconfig.gts.utils.elements.Section;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.GmsTest;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.INativeDevice;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class LinkerConfigTest extends BaseHostJUnit4Test {
+
+    private static final String LINKER_CONFIG_LOCATION = "/linkerconfig/ld.config.txt";
+    private static final String VENDOR_VNDK_LITE = "ro.vndk.lite";
+    private static final String VENDOR_VNDK_VERSION = "ro.vndk.version";
+    private static final int TARGET_MIN_VER = 30; // linkerconfig is available from R
+
+    private static boolean isValidVersion(ITestDevice device) {
+        try {
+            return ApiLevelUtil.isAtLeast(device, TARGET_MIN_VER);
+        } catch (DeviceNotAvailableException e) {
+            fail("There is no available device : " + e.getMessage());
+        }
+
+        return false;
+    }
+
+    private static Configuration loadConfig(INativeDevice targetDevice, String path) {
+        File target = null;
+
+        try {
+            target = targetDevice.pullFile(path);
+        } catch (DeviceNotAvailableException e) {
+            fail("There is no available device : " + e.getMessage());
+        }
+
+        assertTrue("Failed to get linker configuration from expected location",
+                target.exists());
+
+        List<String> lines = new ArrayList<>();
+        try (BufferedReader reader = new BufferedReader(new FileReader(target))) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                line = line.trim();
+                if (!line.isEmpty()) {
+                    lines.add(line);
+                }
+            }
+        } catch (Exception e) {
+            fail("Failed to read file " + path + " with error : " + e.getMessage());
+        }
+
+        return LinkerConfigParser.parseConfiguration(lines);
+    }
+
+    private static void verifyVendorSection(Section section, Set<String> systemAvailableLibraries) {
+        List<Namespace> systemNamespaces = section.namespaces.values().stream()
+                .filter(ns -> ns.searchPaths.stream()
+                        .anyMatch(searchPath -> searchPath.startsWith("/system")
+                                && !searchPath.startsWith("/system/vendor")))
+                .distinct()
+                .collect(Collectors.toList());
+
+        assertFalse("System namespace should not be visible",
+                systemNamespaces.parallelStream().anyMatch(ns -> ns.isVisible));
+
+        section.namespaces.values().forEach((ns) -> {
+            boolean isVendorNamespace = false;
+            for (String libPath : ns.searchPaths) {
+                if (libPath.startsWith(("/vendor")) || libPath.startsWith("/odm")) {
+                    isVendorNamespace = true;
+                    break;
+                }
+            }
+
+            if (!isVendorNamespace) {
+                return;
+            }
+
+            assertThat(
+                    "Vendor libs and System libs should not exist in same namespace : " + ns.name,
+                    systemNamespaces, not(contains(ns)));
+
+            ns.links.values().forEach((link) -> {
+                if (systemNamespaces.contains(link.to)) {
+                    assertFalse(
+                            "It is not allowed to link all shared libs from non-system namespace "
+                                    + link.from
+                                    + "to system namespace " + link.to,
+                            link.allowAll);
+
+                    link.libraries.forEach(library -> {
+                        assertTrue("Library " + library + " is not allowed to use",
+                                systemAvailableLibraries.contains(library));
+                    });
+                }
+            });
+        });
+    }
+
+
+    private static Set<String> loadSystemAvailableLibraries(INativeDevice targetDevice,
+            String vendorVndkVersion) {
+        Set<String> libraries = new HashSet<>();
+
+        // Add Sanitizer libraries
+        libraries.addAll(LibraryListLoader.getLibrariesFromFile(targetDevice,
+                "/system/etc/sanitizer.libraries.txt", true));
+
+        // Add LLNDK libraries
+        libraries.addAll(LibraryListLoader.getLibrariesFromFile(targetDevice,
+                "/apex/com.android.vndk.v" + vendorVndkVersion + "/etc/llndk.libraries."
+                        + vendorVndkVersion + ".txt", true));
+
+        // Add Stub libraries
+        libraries.addAll(LibraryListLoader.STUB_LIBRARIES);
+
+        // Allowed on userdebug/eng for debugging.
+        libraries.add("libfdtrack.so");
+
+        // Add VNDK core variant libraries
+        libraries.addAll(LibraryListLoader.getLibrariesFromFile(targetDevice,
+                    "/system/etc/vndkcorevariant.libraries.txt", false));
+
+        return libraries;
+    }
+
+    @GmsTest(requirement = "GMS-3.5-014")
+    @Test
+    public void shouldHaveLinkerConfigAtExpectedLocation() {
+        ITestDevice targetDevice = getDevice();
+
+        if (!isValidVersion(targetDevice)) {
+            return;
+        }
+
+        try {
+            File linkerConfigFile = targetDevice.pullFile(LINKER_CONFIG_LOCATION);
+            assertTrue("Failed to get linker configuration from expected location",
+                    linkerConfigFile.exists());
+        } catch (DeviceNotAvailableException e) {
+            fail("Target device is not available : " + e.getMessage());
+        }
+    }
+
+    @GmsTest(requirement = "GMS-3.5-014")
+    @Test
+    public void shouldNotAccessSystemFromVendorExceptVndk() {
+        ITestDevice targetDevice = getDevice();
+        boolean vndkLiteEnabled = false;
+
+        try {
+            vndkLiteEnabled = Boolean.parseBoolean(targetDevice.getProperty(VENDOR_VNDK_LITE));
+        } catch (DeviceNotAvailableException e) {
+            fail("Target device is not available : " + e.getMessage());
+        }
+
+        if (!isValidVersion(targetDevice) || vndkLiteEnabled) {
+            return;
+        }
+
+        String vendorVndkVersion = "";
+        try {
+            vendorVndkVersion = targetDevice.getProperty(VENDOR_VNDK_VERSION);
+        } catch (DeviceNotAvailableException e) {
+            fail("Target device is not available : " + e.getMessage());
+        }
+
+        if (vendorVndkVersion == null || vendorVndkVersion.isEmpty()) {
+            return;
+        }
+
+        Configuration conf = loadConfig(targetDevice, LINKER_CONFIG_LOCATION);
+
+        List<Section> vendorSections = conf.dirToSections.entrySet().stream()
+                .filter(item -> item.getKey().startsWith("/vendor") || item.getKey().startsWith(
+                        ("/odm")))
+                .map(Map.Entry::getValue)
+                .distinct()
+                .collect(Collectors.toList());
+
+        assertThat("No section for vendor", vendorSections, not(empty()));
+
+        Set<String> availableLibraries = loadSystemAvailableLibraries(targetDevice,
+                vendorVndkVersion);
+
+        for (Section section : vendorSections) {
+            verifyVendorSection(section, availableLibraries);
+        }
+    }
+}
diff --git a/devicetest/src/android/linkerconfig/gts/utils/LibraryListLoader.java b/devicetest/src/android/linkerconfig/gts/utils/LibraryListLoader.java
new file mode 100644
index 0000000..c12a7c3
--- /dev/null
+++ b/devicetest/src/android/linkerconfig/gts/utils/LibraryListLoader.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2020 Google LLC.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.linkerconfig.gts.utils;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.INativeDevice;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class LibraryListLoader {
+    public static List<String> getLibrariesFromFile(INativeDevice targetDevice,
+            String path, boolean expectFileExists) {
+        List<String> libraries = new ArrayList<>();
+
+        File target = null;
+
+        try {
+            target = targetDevice.pullFile(path);
+        } catch (DeviceNotAvailableException e) {
+            fail("There is no available device : " + e.getMessage());
+        }
+
+        assertTrue("Failed to get library list file from " + path,
+                target.exists() || !expectFileExists);
+
+        if (target.exists()) {
+            try (BufferedReader reader = new BufferedReader(new FileReader(target))) {
+                String library;
+                while ((library = reader.readLine()) != null) {
+                    library = library.trim();
+                    if (!library.isEmpty()) {
+                        libraries.add(library);
+                    }
+                }
+            } catch (Exception e) {
+                fail("Failed to read file " + path + " with error : " + e.getMessage());
+            }
+        }
+
+        return libraries;
+    }
+
+    public static final List<String> STUB_LIBRARIES = Arrays.asList("libEGL.so",
+            "libGLESv1_CM.so",
+            "libGLESv2.so",
+            "libGLESv3.so",
+            "libRS.so",
+            "libaaudio.so",
+            "libadbd_auth.so",
+            "libadbd_fs.so",
+            "libandroid.so",
+            "libandroid_net.so",
+            "libbinder_ndk.so",
+            "libc.so",
+            "libcgrouprc.so",
+            "libclang_rt.asan-arm-android.so",
+            "libclang_rt.asan-i686-android.so",
+            "libclang_rt.asan-x86_64-android.so",
+            "libdl.so",
+            "libdl_android.so",
+            "libft2.so",
+            "libincident.so",
+            "liblog.so",
+            "libm.so",
+            "libmediametrics.so",
+            "libmediandk.so",
+            "libnativewindow.so",
+            "libneuralnetworks_packageinfo.so",
+            "libsync.so",
+            "libvndksupport.so",
+            "libvulkan.so",
+            "libselinux.so");
+}
diff --git a/devicetest/src/android/linkerconfig/gts/utils/LinkerConfigParser.java b/devicetest/src/android/linkerconfig/gts/utils/LinkerConfigParser.java
new file mode 100644
index 0000000..a1db499
--- /dev/null
+++ b/devicetest/src/android/linkerconfig/gts/utils/LinkerConfigParser.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2020 Google LLC.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.linkerconfig.gts.utils;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.linkerconfig.gts.utils.elements.Configuration;
+import android.linkerconfig.gts.utils.elements.Section;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class LinkerConfigParser {
+    static final String REGEX_SECTION_NAME = "\\[\\s*(\\w+)\\s*\\]";
+    static Pattern sSectionNamePattern = Pattern.compile(REGEX_SECTION_NAME);
+
+    public static Configuration parseConfiguration(List<String> contents) {
+        Configuration configuration = new Configuration();
+        Section currentSection = null;
+
+        for (String content : contents) {
+            if (content.isEmpty()) {
+                continue;
+            }
+
+            Matcher sectionNameMatcher = sSectionNamePattern.matcher(content);
+            if (sectionNameMatcher.matches()) {
+                String sectionName = sectionNameMatcher.group(1);
+                assertTrue("Section should have been defined from dir to section map",
+                        configuration.sections.containsKey(sectionName));
+                currentSection = configuration.getSectionFromName(sectionName);
+                continue;
+            }
+
+            if (currentSection == null && configuration.parseConfiguration(content)) {
+                continue;
+            }
+
+            assertNotNull("This line cannot be parsed without section : " + content,
+                    currentSection);
+
+            currentSection.parseConfiguration(content);
+        }
+
+        return configuration;
+    }
+}
diff --git a/devicetest/src/android/linkerconfig/gts/utils/elements/Configuration.java b/devicetest/src/android/linkerconfig/gts/utils/elements/Configuration.java
new file mode 100644
index 0000000..9ee3278
--- /dev/null
+++ b/devicetest/src/android/linkerconfig/gts/utils/elements/Configuration.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 Google LLC.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.linkerconfig.gts.utils.elements;
+
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class Configuration {
+    private static final String REGEX_DIR_TO_SECTION = "dir\\.(\\w+)\\s*=\\s([\\w.\\-/]+)";
+    private Pattern mDirToSectionPattern = Pattern.compile(REGEX_DIR_TO_SECTION);
+
+    public Map<String, Section> sections = new HashMap<>();
+    public Map<String, Section> dirToSections = new HashMap<>();
+
+    public Section getSectionFromName(String name) {
+        if (sections.containsKey(name)) {
+            return sections.get(name);
+        }
+
+        Section s = new Section();
+        s.name = name;
+        sections.put(name, s);
+
+        return s;
+    }
+
+    public boolean parseConfiguration(String dirDefinition) {
+        Matcher dirToSectionMatcher = mDirToSectionPattern.matcher(dirDefinition);
+
+        if (!dirToSectionMatcher.matches()) {
+            return false;
+        }
+
+        String sectionName = dirToSectionMatcher.group(1);
+        String dirPath = dirToSectionMatcher.group(2);
+        Section section = getSectionFromName(sectionName);
+        dirToSections.put(dirPath, section);
+
+        return true;
+    }
+}
diff --git a/devicetest/src/android/linkerconfig/gts/utils/elements/Link.java b/devicetest/src/android/linkerconfig/gts/utils/elements/Link.java
new file mode 100644
index 0000000..045b47b
--- /dev/null
+++ b/devicetest/src/android/linkerconfig/gts/utils/elements/Link.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 Google LLC.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.linkerconfig.gts.utils.elements;
+
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Link {
+    public Namespace from, to;
+    public List<String> libraries = new ArrayList<>();
+    public boolean allowAll;
+}
diff --git a/devicetest/src/android/linkerconfig/gts/utils/elements/Namespace.java b/devicetest/src/android/linkerconfig/gts/utils/elements/Namespace.java
new file mode 100644
index 0000000..25649e1
--- /dev/null
+++ b/devicetest/src/android/linkerconfig/gts/utils/elements/Namespace.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 Google LLC.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.linkerconfig.gts.utils.elements;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class Namespace {
+    public String name;
+    public boolean isVisible;
+    public boolean isIsolated;
+
+    public List<String> searchPaths = new ArrayList<>();
+    public List<String> permittedPaths = new ArrayList<>();
+    public Map<String, Link> links = new HashMap<>();
+}
diff --git a/devicetest/src/android/linkerconfig/gts/utils/elements/Section.java b/devicetest/src/android/linkerconfig/gts/utils/elements/Section.java
new file mode 100644
index 0000000..a63b1aa
--- /dev/null
+++ b/devicetest/src/android/linkerconfig/gts/utils/elements/Section.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2020 Google LLC.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.linkerconfig.gts.utils.elements;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class Section {
+    public Section() {
+        Namespace defaultNamespace = new Namespace();
+        defaultNamespace.name = "default";
+
+        namespaces.put("default", defaultNamespace);
+    }
+
+    static final String REGEX_ADDITIONAL_NAMESPACES =
+            "additional\\.namespaces\\s*=\\s*((?:[\\w]+)(?:,[\\w]+)*)";
+    static final String REGEX_NAMESPACE =
+            "namespace\\.(\\w+)\\.([^\\s=]+)\\s*(?:=|\\+=)\\s*([^\\s]+)";
+
+    static Pattern sAdditionalNamespacesPattern = Pattern.compile(REGEX_ADDITIONAL_NAMESPACES);
+    static Pattern sNamespacePattern = Pattern.compile(REGEX_NAMESPACE);
+
+    public String name;
+    public Map<String, Namespace> namespaces = new HashMap<>();
+
+
+    public Namespace getNamespaceFromName(String namespace) {
+        assertTrue("Failed to find namespace " + namespace + " from section " + name,
+                namespaces.containsKey(namespace));
+
+        return namespaces.get(namespace);
+    }
+
+    public void parseConfiguration(String line) {
+        Matcher additionalNamespacesMatcher = sAdditionalNamespacesPattern.matcher(line);
+        if (additionalNamespacesMatcher.matches()) {
+            assertEquals("Additional namespaces are already defined.", 1, namespaces.size());
+            String additionalNamespaces = additionalNamespacesMatcher.group(1);
+            for (String additionalNamespace : additionalNamespaces.split(",")) {
+                Namespace ns = new Namespace();
+                ns.name = additionalNamespace;
+                namespaces.put(additionalNamespace, ns);
+            }
+
+            return;
+        }
+
+        Matcher namespaceMatcher = sNamespacePattern.matcher(line);
+        assertTrue("Cannot parse : " + line, namespaceMatcher.matches());
+
+        String targetNamespace = namespaceMatcher.group(1);
+        String[] commands = namespaceMatcher.group(2).split("\\.");
+        String value = namespaceMatcher.group(3);
+
+        assertTrue("Cannot find namespace " + targetNamespace,
+                namespaces.containsKey(targetNamespace));
+
+        Namespace ns = getNamespaceFromName(targetNamespace);
+
+        assertTrue("Invalid command : " + namespaceMatcher.group(2) + " from " + line,
+                commands.length > 0);
+        switch (commands[0]) {
+            case "isolated":
+                assertEquals("Invalid command : " + line, 1, commands.length);
+                ns.isIsolated = Boolean.parseBoolean(value);
+                return;
+            case "visible":
+                assertEquals("Invalid command : " + line, 1, commands.length);
+                ns.isVisible = Boolean.parseBoolean(value);
+                return;
+            case "search":
+                assertEquals("Invalid command : " + line, 2, commands.length);
+                assertEquals("Invalid command : " + line, "paths", commands[1]);
+                if (!value.isEmpty()) {
+                    ns.searchPaths.add(value);
+                }
+                return;
+            case "permitted":
+                assertEquals("Invalid command : " + line, 2, commands.length);
+                assertEquals("Invalid command : " + line, "paths", commands[1]);
+                if (!value.isEmpty()) {
+                    ns.permittedPaths.add(value);
+                }
+                return;
+            case "asan":
+            case "hwasan":
+                // Do not parse configuration for hwasan or asan in this test
+                assertEquals("Invalid command : " + line, 3, commands.length);
+                assertTrue("Invalid command : " + line,
+                        commands[1].equals("search") || commands[1].equals("permitted"));
+                assertEquals("Invalid command : " + line, "paths", commands[2]);
+                return;
+            case "links":
+                assertEquals("Invalid command : " + line, 1, commands.length);
+                for (String linkTarget : value.split(",")) {
+                    assertTrue("Invalid target namespace : " + linkTarget + " from " + line,
+                            namespaces.containsKey(linkTarget));
+                    Link link = new Link();
+                    link.from = ns;
+                    link.to = getNamespaceFromName(linkTarget);
+                    ns.links.put(linkTarget, link);
+                }
+                return;
+            case "link":
+                assertEquals("Invalid command : " + line, 3, commands.length);
+                String linkTarget = commands[1];
+                assertTrue("Link not defined : " + linkTarget + " from " + line,
+                        ns.links.containsKey(linkTarget));
+                Link link = ns.links.get(linkTarget);
+
+                if (commands[2].equals("allow_all_shared_libs")) {
+                    link.allowAll = Boolean.parseBoolean(value);
+                } else if (commands[2].equals("shared_libs")) {
+                    String[] libs = value.split(":");
+                    for (String lib : libs) {
+                        link.libraries.add(lib);
+                    }
+                } else {
+                    fail("Invalid link command : " + line);
+                }
+                return;
+            case "whitelisted":
+            case "allowed_libs":
+                assertEquals("Invalid command : " + line, 1, commands.length);
+                // Do not parse allowed library list in this test
+                return;
+        }
+
+
+        fail("Unable to parse command : " + line);
+        return;
+    }
+}