Refactor derive_sdk to be more testable

Refactor the code into a reusable library that is used from the
derive_sdk binary as well as a newly added cc_test.

The code now accepts an argument for which dir to scan for mounted
modules.

Add a simple test for the current system image R SDK version for now.

Bug: 173188089
Test: atest derive_sdk_test
Change-Id: If7442d515d55b35174d396cc579ee2c9865da2b5
diff --git a/derive_sdk/Android.bp b/derive_sdk/Android.bp
index 3e030e8..dc4e352 100644
--- a/derive_sdk/Android.bp
+++ b/derive_sdk/Android.bp
@@ -14,10 +14,6 @@
 
 cc_defaults {
     name: "derive_sdk-defaults",
-    srcs: [
-        "derive_sdk.cpp",
-        "sdk.proto",
-    ],
     proto: {
         type: "lite",
         static: true,
@@ -29,26 +25,56 @@
     static_libs: [
         "libbase",
         "libmodules-utils-build",
+        "libprotobuf-cpp-lite",
     ],
 }
 
+cc_library {
+    name: "libderive_sdk",
+    srcs: [
+        "derive_sdk.cpp",
+        "sdk.proto",
+    ],
+    defaults: ["derive_sdk-defaults"],
+    apex_available: ["com.android.sdkext"],
+}
+
+cc_defaults {
+    name: "derive_sdk_binary-defaults",
+    defaults: ["derive_sdk-defaults"],
+    srcs: ["main.cpp"],
+    static_libs: ["libderive_sdk"],
+
+}
 cc_binary {
     name: "derive_sdk",
-    defaults: [ "derive_sdk-defaults" ],
-    apex_available: [ "com.android.sdkext" ],
+    defaults: ["derive_sdk_binary-defaults"],
+    apex_available: ["com.android.sdkext"],
 }
 
 // Work around testing using a 64-bit test suite on 32-bit test device by
 // using a prefer32 version of derive_sdk in testing.
 cc_binary {
     name: "derive_sdk_prefer32",
-    defaults: [ "derive_sdk-defaults" ],
+    defaults: ["derive_sdk_binary-defaults"],
     compile_multilib: "prefer32",
     stem: "derive_sdk",
-    apex_available: [ "test_com.android.sdkext" ],
+    apex_available: ["test_com.android.sdkext"],
     installable: false,
 }
 
+cc_test {
+    name: "derive_sdk_test",
+    defaults: ["derive_sdk-defaults"],
+    srcs: [
+        "derive_sdk_test.cpp",
+        "sdk.proto",
+    ],
+    require_root: true,
+    static_libs: ["libderive_sdk"],
+    test_suites: ["device-tests"],
+}
+
 prebuilt_etc {
     name: "derive_sdk.rc",
     src: "derive_sdk.rc",
diff --git a/derive_sdk/derive_sdk.cpp b/derive_sdk/derive_sdk.cpp
index 1b85c90..fb5f1f5 100644
--- a/derive_sdk/derive_sdk.cpp
+++ b/derive_sdk/derive_sdk.cpp
@@ -16,6 +16,8 @@
 
 #define LOG_TAG "derive_sdk"
 
+#include "derive_sdk.h"
+
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/properties.h>
@@ -31,11 +33,15 @@
 
 using com::android::sdkext::proto::SdkVersion;
 
-int main(int, char**) {
-  std::unique_ptr<DIR, decltype(&closedir)> apex(opendir("/apex"), closedir);
+namespace android {
+namespace derivesdk {
+
+bool SetSdkLevels(const std::string& mountpath) {
+  std::unique_ptr<DIR, decltype(&closedir)> apex(opendir(mountpath.c_str()),
+                                                 closedir);
   if (!apex) {
-    LOG(ERROR) << "Could not read /apex";
-    return EXIT_FAILURE;
+    LOG(ERROR) << "Could not read " + mountpath;
+    return false;
   }
   struct dirent* de;
   std::vector<std::string> paths;
@@ -45,7 +51,7 @@
       // Skip <name>@<ver> dirs, as they are bind-mounted to <name>
       continue;
     }
-    std::string path = "/apex/" + name + "/etc/sdkinfo.binarypb";
+    std::string path = mountpath + "/" + name + "/etc/sdkinfo.binarypb";
     struct stat statbuf;
     if (stat(path.c_str(), &statbuf) == 0) {
       paths.push_back(path);
@@ -72,15 +78,18 @@
 
   if (!android::base::SetProperty("build.version.extensions.r", prop_value)) {
     LOG(ERROR) << "failed to set r sdk_info prop";
-    return EXIT_FAILURE;
+    return false;
   }
   if (android::modules::sdklevel::IsAtLeastS()) {
     if (!android::base::SetProperty("build.version.extensions.s", prop_value)) {
       LOG(ERROR) << "failed to set s sdk_info prop";
-      return EXIT_FAILURE;
+      return false;
     }
   }
 
   LOG(INFO) << "Extension version is " << prop_value;
-  return EXIT_SUCCESS;
+  return true;
 }
+
+}  // namespace derivesdk
+}  // namespace android
diff --git a/derive_sdk/derive_sdk.h b/derive_sdk/derive_sdk.h
new file mode 100644
index 0000000..2d64b7c
--- /dev/null
+++ b/derive_sdk/derive_sdk.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 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 <string>
+
+namespace android {
+namespace derivesdk {
+
+bool SetSdkLevels(const std::string& mountpath);
+
+}  // namespace derivesdk
+}  // namespace android
diff --git a/derive_sdk/derive_sdk_test.cpp b/derive_sdk/derive_sdk_test.cpp
new file mode 100644
index 0000000..5dbfdb7
--- /dev/null
+++ b/derive_sdk/derive_sdk_test.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 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 "derive_sdk.h"
+
+#include <android-base/properties.h>
+#include <gtest/gtest.h>
+
+#include <cstdlib>
+
+class DeriveSdkTest : public ::testing::Test {
+ protected:
+  void TearDown() override { android::derivesdk::SetSdkLevels("/apex"); }
+};
+
+int R() {
+  return android::base::GetIntProperty("build.version.extensions.r", -1);
+}
+
+TEST_F(DeriveSdkTest, CurrentSystemImageValue) { EXPECT_EQ(R(), 0); }
+
+int main(int argc, char **argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/derive_sdk/main.cpp b/derive_sdk/main.cpp
new file mode 100644
index 0000000..d7b0e7a
--- /dev/null
+++ b/derive_sdk/main.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2020 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 <cstdlib>
+
+#include "derive_sdk.h"
+
+int main(int, char**) {
+  if (!android::derivesdk::SetSdkLevels("/apex")) {
+    return EXIT_FAILURE;
+  }
+  return EXIT_SUCCESS;
+}