Add a Compilation OS mode to odrefresh.

When the --compilation-os-mode is set, odrefresh should:
- Unconditionally compile everything
- Write `compilationOsMode="true"` to the cache info and omit
  `lastUpdateMillis`
- On the next boot, `--check` returns `kCompilationRequired`, and
  `--compile` updates the cache info to fill `lastUpdateMillis` but does
  not actually re-compile anything

Bug: 211458160
Test: odsign_e2e_tests
Change-Id: Ie913846d2000f9b57aa0e5133f9d25eb24aa0052
diff --git a/odrefresh/CacheInfo.xsd b/odrefresh/CacheInfo.xsd
index 3aa94a5..ff83914 100644
--- a/odrefresh/CacheInfo.xsd
+++ b/odrefresh/CacheInfo.xsd
@@ -24,6 +24,8 @@
     `/data/misc/apexdata/com.android.art/dalvik-cache` -->
   <xs:element name="cacheInfo">
     <xs:complexType>
+    <!-- True if the cache info is generated in the Compilation OS. -->
+    <xs:attribute name="compilationOsMode" type="xs:boolean" />
     <xs:sequence>
       <xs:element name="artModuleInfo" minOccurs="1" maxOccurs="1" type="t:moduleInfo" />
       <xs:element name="moduleInfoList" minOccurs="1" maxOccurs="1" type="t:moduleInfoList" />
@@ -49,8 +51,9 @@
     <xs:attribute name="versionCode" type="xs:long" use="required" />
     <!-- Module versionName for the active APEX from `/apex/apex-info-list.xml`. -->
     <xs:attribute name="versionName" type="xs:string" use="required" />
-    <!-- Module lastUpdateMillis for the active APEX from `/apex/apex-info-list.xml`. -->
-    <xs:attribute name="lastUpdateMillis" type="xs:long" use="required" />
+    <!-- Module lastUpdateMillis for the active APEX from `/apex/apex-info-list.xml`. This field is
+      not set if the cache info is generated in Compilation OS. -->
+    <xs:attribute name="lastUpdateMillis" type="xs:long" />
   </xs:complexType>
 
   <!-- Components of a classpath. -->
diff --git a/odrefresh/odr_config.h b/odrefresh/odr_config.h
index af7c0a7..855b7a2 100644
--- a/odrefresh/odr_config.h
+++ b/odrefresh/odr_config.h
@@ -72,6 +72,7 @@
   time_t max_execution_seconds_ = kMaximumExecutionSeconds;
   time_t max_child_process_seconds_ = kMaxChildProcessSeconds;
   std::string standalone_system_server_jars_;
+  bool compilation_os_mode_;
 
   // Staging directory for artifacts. The directory must exist and will be automatically removed
   // after compilation. If empty, use the default directory.
@@ -154,6 +155,7 @@
   }
   time_t GetMaxExecutionSeconds() const { return max_execution_seconds_; }
   time_t GetMaxChildProcessSeconds() const { return max_child_process_seconds_; }
+  bool GetCompilationOsMode() const { return compilation_os_mode_; }
 
   void SetApexInfoListFile(const std::string& file_path) { apex_info_list_file_ = file_path; }
   void SetArtBinDir(const std::string& art_bin_dir) { art_bin_dir_ = art_bin_dir; }
@@ -204,6 +206,8 @@
     standalone_system_server_jars_ = jars;
   }
 
+  void SetCompilationOsMode(bool value) { compilation_os_mode_ = value; }
+
  private:
   // Returns a pair for the possible instruction sets for the configured instruction set
   // architecture. The first item is the 32-bit architecture and the second item is the 64-bit
diff --git a/odrefresh/odrefresh.cc b/odrefresh/odrefresh.cc
index 9517e54..767f406 100644
--- a/odrefresh/odrefresh.cc
+++ b/odrefresh/odrefresh.cc
@@ -184,24 +184,26 @@
 
 // Returns cache provenance information based on the current APEX version and filesystem
 // information.
-art_apex::ModuleInfo GenerateModuleInfo(const apex::ApexInfo& apex_info) {
+art_apex::ModuleInfo GenerateModuleInfo(const apex::ApexInfo& apex_info,
+                                        bool include_last_update_millis) {
   // The lastUpdateMillis is an addition to ApexInfoList.xsd to support samegrade installs.
   int64_t last_update_millis =
       apex_info.hasLastUpdateMillis() ? apex_info.getLastUpdateMillis() : 0;
-  return art_apex::ModuleInfo{apex_info.getModuleName(),
-                              apex_info.getVersionCode(),
-                              apex_info.getVersionName(),
-                              last_update_millis};
+  return art_apex::ModuleInfo{
+      apex_info.getModuleName(),
+      apex_info.getVersionCode(),
+      apex_info.getVersionName(),
+      include_last_update_millis ? std::make_optional(last_update_millis) : std::nullopt};
 }
 
 // Returns cache provenance information for all APEXes.
 std::vector<art_apex::ModuleInfo> GenerateModuleInfoList(
-    const std::vector<apex::ApexInfo>& apex_info_list) {
+    const std::vector<apex::ApexInfo>& apex_info_list, bool include_last_update_millis) {
   std::vector<art_apex::ModuleInfo> module_info_list;
   std::transform(apex_info_list.begin(),
                  apex_info_list.end(),
                  std::back_inserter(module_info_list),
-                 GenerateModuleInfo);
+                 std::bind(GenerateModuleInfo, std::placeholders::_1, include_last_update_millis));
   return module_info_list;
 }
 
@@ -596,6 +598,9 @@
       }
     }
   }
+  // The ART APEX is always relevant no matter it contains any compilable JAR or not, because it
+  // contains the runtime.
+  relevant_apexes.insert("com.android.art");
 
   std::vector<apex::ApexInfo> filtered_info_list;
   std::copy_if(info_list->getApexInfo().begin(),
@@ -610,64 +615,74 @@
   return art_apex::read(cache_info_filename_.c_str());
 }
 
-void OnDeviceRefresh::WriteCacheInfo() const {
+Result<void> OnDeviceRefresh::WriteCacheInfo() const {
   if (OS::FileExists(cache_info_filename_.c_str())) {
     if (unlink(cache_info_filename_.c_str()) != 0) {
-      PLOG(ERROR) << "Failed to unlink() file " << QuotePath(cache_info_filename_);
+      return ErrnoErrorf("Failed to unlink() file {}", QuotePath(cache_info_filename_));
     }
   }
 
   const std::string dir_name = android::base::Dirname(cache_info_filename_);
   if (!EnsureDirectoryExists(dir_name)) {
-    LOG(ERROR) << "Could not create directory: " << QuotePath(dir_name);
-    return;
+    return Errorf("Could not create directory {}", QuotePath(dir_name));
   }
 
   std::optional<std::vector<apex::ApexInfo>> apex_info_list = GetApexInfoList();
   if (!apex_info_list.has_value()) {
-    LOG(ERROR) << "Could not update " << QuotePath(cache_info_filename_) << " : no APEX info";
-    return;
+    return Errorf("Could not update {}: no APEX info", QuotePath(cache_info_filename_));
   }
 
   std::optional<apex::ApexInfo> art_apex_info = GetArtApexInfo(apex_info_list.value());
   if (!art_apex_info.has_value()) {
-    LOG(ERROR) << "Could not update " << QuotePath(cache_info_filename_) << " : no ART APEX info";
-    return;
+    return Errorf("Could not update {}: no ART APEX info", QuotePath(cache_info_filename_));
   }
 
-  art_apex::ModuleInfo art_module_info = GenerateModuleInfo(art_apex_info.value());
+  // We don't write lastUpdateMillis to the cache info in Compilation OS because APEX timestamps
+  // inside the VM are different from those outside (b/211458160).
+  bool include_last_update_millis = !config_.GetCompilationOsMode();
+  art_apex::ModuleInfo art_module_info =
+      GenerateModuleInfo(art_apex_info.value(), include_last_update_millis);
   std::vector<art_apex::ModuleInfo> module_info_list =
-      GenerateModuleInfoList(apex_info_list.value());
+      GenerateModuleInfoList(apex_info_list.value(), include_last_update_millis);
 
   std::optional<std::vector<art_apex::Component>> bcp_components =
       GenerateBootClasspathComponents();
   if (!bcp_components.has_value()) {
-    LOG(ERROR) << "No boot classpath components.";
-    return;
+    return Errorf("No boot classpath components.");
   }
 
   std::optional<std::vector<art_apex::Component>> bcp_compilable_components =
       GenerateBootExtensionCompilableComponents();
   if (!bcp_compilable_components.has_value()) {
-    LOG(ERROR) << "No boot classpath extension compilable components.";
-    return;
+    return Errorf("No boot classpath extension compilable components.");
   }
 
   std::optional<std::vector<art_apex::SystemServerComponent>> system_server_components =
       GenerateSystemServerComponents();
   if (!system_server_components.has_value()) {
-    LOG(ERROR) << "No system_server extension components.";
-    return;
+    return Errorf("No system_server extension components.");
   }
 
   std::ofstream out(cache_info_filename_.c_str());
-  art_apex::CacheInfo info({art_module_info},
-                           {art_apex::ModuleInfoList(module_info_list)},
-                           {art_apex::Classpath(bcp_components.value())},
-                           {art_apex::Classpath(bcp_compilable_components.value())},
-                           {art_apex::SystemServerComponents(system_server_components.value())});
+  if (out.fail()) {
+    return Errorf("Cannot open {} for writing.", QuotePath(cache_info_filename_));
+  }
+
+  art_apex::CacheInfo info(
+      {art_module_info},
+      {art_apex::ModuleInfoList(module_info_list)},
+      {art_apex::Classpath(bcp_components.value())},
+      {art_apex::Classpath(bcp_compilable_components.value())},
+      {art_apex::SystemServerComponents(system_server_components.value())},
+      config_.GetCompilationOsMode() ? std::make_optional(true) : std::nullopt);
 
   art_apex::write(out, info);
+  out.close();
+  if (out.fail()) {
+    return Errorf("Cannot write to {}", QuotePath(cache_info_filename_));
+  }
+
+  return {};
 }
 
 static void ReportNextBootAnimationProgress(uint32_t current_compilation,
@@ -816,16 +831,21 @@
     return false;
   }
 
-  // Check lastUpdateMillis for samegrade installs. If `cached_art_info` is missing the
-  // lastUpdateMillis field then it is not current with the schema used by this binary so treat
-  // it as a samegrade update. Otherwise check whether the lastUpdateMillis changed.
-  const int64_t cached_art_last_update_millis =
-      cached_art_info->hasLastUpdateMillis() ? cached_art_info->getLastUpdateMillis() : -1;
-  if (cached_art_last_update_millis != art_apex_info.getLastUpdateMillis()) {
-    LOG(INFO) << "ART APEX last update time mismatch (" << cached_art_last_update_millis
-              << " != " << art_apex_info.getLastUpdateMillis() << ").";
-    metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
-    return false;
+  // We don't check lastUpdateMillis if the cache info is generated in Compilation OS because APEX
+  // timestamps inside the VM are different from those outside (b/211458160). The cache info will be
+  // updated in the `--compile` phase to fill this field.
+  if (!(cache_info->hasCompilationOsMode() && cache_info->getCompilationOsMode())) {
+    // Check lastUpdateMillis for samegrade installs. If `cached_art_info` is missing the
+    // lastUpdateMillis field then it is not current with the schema used by this binary so treat
+    // it as a samegrade update. Otherwise check whether the lastUpdateMillis changed.
+    const int64_t cached_art_last_update_millis =
+        cached_art_info->hasLastUpdateMillis() ? cached_art_info->getLastUpdateMillis() : -1;
+    if (cached_art_last_update_millis != art_apex_info.getLastUpdateMillis()) {
+      LOG(INFO) << "ART APEX last update time mismatch (" << cached_art_last_update_millis
+                << " != " << art_apex_info.getLastUpdateMillis() << ").";
+      metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
+      return false;
+    }
   }
 
   // Check boot class components.
@@ -960,13 +980,15 @@
       return compile_all();
     }
 
-    if (!cached_module_info->hasLastUpdateMillis() ||
-        cached_module_info->getLastUpdateMillis() != current_apex_info.getLastUpdateMillis()) {
-      LOG(INFO) << "APEX (" << apex_name << ") last update time mismatch ("
-                << cached_module_info->getLastUpdateMillis()
-                << " != " << current_apex_info.getLastUpdateMillis() << ").";
-      metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
-      return compile_all();
+    if (!(cache_info->hasCompilationOsMode() && cache_info->getCompilationOsMode())) {
+      if (!cached_module_info->hasLastUpdateMillis() ||
+          cached_module_info->getLastUpdateMillis() != current_apex_info.getLastUpdateMillis()) {
+        LOG(INFO) << "APEX (" << apex_name << ") last update time mismatch ("
+                  << cached_module_info->getLastUpdateMillis()
+                  << " != " << current_apex_info.getLastUpdateMillis() << ").";
+        metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
+        return compile_all();
+      }
     }
   }
 
@@ -1148,6 +1170,13 @@
     return RemoveArtifactsDirectory() ? ExitCode::kCompilationRequired : ExitCode::kCleanupFailed;
   };
 
+  // Compilation OS cannot reuse the artifacts generated before because it cannot securely verify
+  // them (b/203630168). Therefore, we always compile everything in Compilation OS. This is fine
+  // because Compilation OS runs when the device is idling and it does not affact boot time.
+  if (config_.GetCompilationOsMode()) {
+    return cleanup_and_compile_all();
+  }
+
   std::optional<std::vector<apex::ApexInfo>> apex_info_list = GetApexInfoList();
   if (!apex_info_list.has_value()) {
     // This should never happen, further up-to-date checks are not possible if it does.
@@ -1225,6 +1254,18 @@
     return ExitCode::kCleanupFailed;
   }
 
+  // If the cache info was genereted in Compilation OS, update it to fill the lastUpdateMillis
+  // field.
+  if (!compilation_required && cache_info->hasCompilationOsMode() &&
+      cache_info->getCompilationOsMode()) {
+    compilation_options->update_cache_info_only = true;
+    // Return `kCompilationRequired` though there is nothing to compile. This is needed so that
+    // odsign will invoke `odrefresh --compile` and therefore we can update the cache info in that
+    // phase. We cannot update the cache info here because `odrefresh --check` should not modify any
+    // file. Otherwise, it will break odsign's signature verification.
+    compilation_required = true;
+  }
+
   return compilation_required ? ExitCode::kCompilationRequired : ExitCode::kOkay;
 }
 
@@ -1508,6 +1549,18 @@
     }
   }
 
+  // Emit cache info before compiling. This can be used to throttle compilation attempts later.
+  Result<void> result = WriteCacheInfo();
+  if (!result.ok()) {
+    LOG(ERROR) << result.error();
+    return ExitCode::kCleanupFailed;
+  }
+
+  if (compilation_options.update_cache_info_only) {
+    metrics.SetStage(OdrMetrics::Stage::kComplete);
+    return ExitCode::kCompilationSuccess;
+  }
+
   if (!config_.GetStagingDir().empty()) {
     staging_dir = config_.GetStagingDir().c_str();
   } else {
@@ -1518,9 +1571,6 @@
     }
   }
 
-  // Emit cache info before compiling. This can be used to throttle compilation attempts later.
-  WriteCacheInfo();
-
   std::string error_msg;
 
   uint32_t dex2oat_invocation_count = 0;
diff --git a/odrefresh/odrefresh.h b/odrefresh/odrefresh.h
index 0319094..bc6a68f 100644
--- a/odrefresh/odrefresh.h
+++ b/odrefresh/odrefresh.h
@@ -40,6 +40,9 @@
 namespace odrefresh {
 
 struct CompilationOptions {
+  // If true, update the cache info only and do not compile anything.
+  bool update_cache_info_only;
+
   // If not empty, compile the bootclasspath extensions for ISAs in the list.
   std::vector<InstructionSet> compile_boot_extensions_for_isas;
 
@@ -86,8 +89,8 @@
   // Reads the ART APEX cache information (if any) found in the output artifact directory.
   std::optional<com::android::art::CacheInfo> ReadCacheInfo() const;
 
-  // Write ART APEX cache information to `kOnDeviceRefreshOdrefreshArtifactDirectory`.
-  void WriteCacheInfo() const;
+  // Writes ART APEX cache information to `kOnDeviceRefreshOdrefreshArtifactDirectory`.
+  android::base::Result<void> WriteCacheInfo() const;
 
   std::vector<com::android::art::Component> GenerateBootClasspathComponents() const;
 
diff --git a/odrefresh/odrefresh_main.cc b/odrefresh/odrefresh_main.cc
index e4ef25a..47ac9f0 100644
--- a/odrefresh/odrefresh_main.cc
+++ b/odrefresh/odrefresh_main.cc
@@ -135,6 +135,8 @@
         ArgumentError("Failed to parse CID: %s", value.c_str());
       }
       config->SetCompilationOsAddress(cid);
+    } else if (ArgumentEquals(arg, "--compilation-os-mode")) {
+      config->SetCompilationOsMode(true);
     } else if (ArgumentMatches(arg, "--dalvik-cache=", &value)) {
       art::OverrideDalvikCacheSubDirectory(value);
       config->SetArtifactDirectory(GetApexDataDalvikCacheDirectory(art::InstructionSet::kNone));
@@ -193,6 +195,8 @@
   UsageError("--no-refresh                     Do not refresh existing artifacts.");
   UsageError("--use-compilation-os=<CID>       Run compilation in the VM with the given CID.");
   UsageError("                                 (0 = do not use VM, -1 = use composd's VM)");
+  UsageError("--compilation-os-mode            Indicate that odrefresh is running in Compilation");
+  UsageError("                                 OS.");
   UsageError("--dalvik-cache=<DIR>             Write artifacts to .../<DIR> rather than");
   UsageError("                                 .../dalvik-cache");
   UsageError("--max-execution-seconds=<N>      Maximum timeout of all compilation combined");
diff --git a/odrefresh/odrefresh_test.cc b/odrefresh/odrefresh_test.cc
index 4d70a66..7494084 100644
--- a/odrefresh/odrefresh_test.cc
+++ b/odrefresh/odrefresh_test.cc
@@ -28,6 +28,7 @@
 #include "aidl/com/android/art/DexoptBcpExtArgs.h"
 #include "aidl/com/android/art/DexoptSystemServerArgs.h"
 #include "aidl/com/android/art/Isa.h"
+#include "android-base/file.h"
 #include "android-base/parseint.h"
 #include "android-base/scopeguard.h"
 #include "android-base/stringprintf.h"
@@ -107,6 +108,25 @@
   return ExplainMatchResult(matcher, path_str, result_listener);
 }
 
+void WriteFakeApexInfoList(const std::string& filename) {
+  std::string content = R"xml(
+<?xml version="1.0" encoding="utf-8"?>
+<apex-info-list>
+  <apex-info
+      moduleName="com.android.art"
+      modulePath="/data/apex/active/com.android.art@319999900.apex"
+      preinstalledModulePath="/system/apex/com.android.art.capex"
+      versionCode="319999900"
+      versionName=""
+      isFactory="false"
+      isActive="true"
+      lastUpdateMillis="12345678">
+  </apex-info>
+</apex-info-list>
+)xml";
+  android::base::WriteStringToFile(content, filename);
+}
+
 class OdRefreshTest : public CommonArtTest {
  public:
   OdRefreshTest() : config_("odrefresh") {}
@@ -163,7 +183,10 @@
     ASSERT_TRUE(EnsureDirectoryExists(javalib_dir));
     CreateEmptyFile(boot_art);
 
-    config_.SetApexInfoListFile(Concatenate({temp_dir_path, "/apex-info-list.xml"}));
+    std::string apex_info_filename = Concatenate({temp_dir_path, "/apex-info-list.xml"});
+    WriteFakeApexInfoList(apex_info_filename);
+    config_.SetApexInfoListFile(apex_info_filename);
+
     config_.SetArtBinDir(Concatenate({temp_dir_path, "/bin"}));
     config_.SetBootClasspath(framework_jar_);
     config_.SetDex2oatBootclasspath(framework_jar_);
@@ -172,6 +195,7 @@
     config_.SetIsa(InstructionSet::kX86_64);
     config_.SetZygoteKind(ZygoteKind::kZygote64_32);
     config_.SetSystemServerCompilerFilter("speed");  // specify a default
+    config_.SetArtifactDirectory(dalvik_cache_dir_);
 
     std::string staging_dir = dalvik_cache_dir_ + "/staging";
     ASSERT_TRUE(EnsureDirectoryExists(staging_dir));
@@ -286,6 +310,7 @@
 TEST_F(OdRefreshTest, MissingStandaloneSystemServerJars) {
   config_.SetStandaloneSystemServerJars("");
   auto [odrefresh, mock_odr_dexopt] = CreateOdRefresh();
+  EXPECT_CALL(*mock_odr_dexopt, DoDexoptSystemServer).WillRepeatedly(Return(0));
   EXPECT_EQ(
       odrefresh->Compile(*metrics_,
                          CompilationOptions{
diff --git a/odrefresh/schema/current.txt b/odrefresh/schema/current.txt
index a7aa52e..958f2a0 100644
--- a/odrefresh/schema/current.txt
+++ b/odrefresh/schema/current.txt
@@ -5,11 +5,13 @@
     ctor public CacheInfo();
     method public com.android.art.ModuleInfo getArtModuleInfo();
     method public com.android.art.Classpath getBootClasspath();
+    method public boolean getCompilationOsMode();
     method public com.android.art.Classpath getDex2oatBootClasspath();
     method public com.android.art.ModuleInfoList getModuleInfoList();
     method public com.android.art.SystemServerComponents getSystemServerComponents();
     method public void setArtModuleInfo(com.android.art.ModuleInfo);
     method public void setBootClasspath(com.android.art.Classpath);
+    method public void setCompilationOsMode(boolean);
     method public void setDex2oatBootClasspath(com.android.art.Classpath);
     method public void setModuleInfoList(com.android.art.ModuleInfoList);
     method public void setSystemServerComponents(com.android.art.SystemServerComponents);
diff --git a/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java b/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java
index 567c85d..fc8e57f 100644
--- a/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java
+++ b/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java
@@ -45,8 +45,9 @@
 public class OdrefreshHostTest extends BaseHostJUnit4Test {
     private static final String CACHE_INFO_FILE =
             OdsignTestUtils.ART_APEX_DALVIK_CACHE_DIRNAME + "/cache-info.xml";
+    private static final String ODREFRESH_BIN = "odrefresh";
     private static final String ODREFRESH_COMMAND =
-            "odrefresh --partial-compilation --no-refresh --compile";
+            ODREFRESH_BIN + " --partial-compilation --no-refresh --compile";
 
     private static OdsignTestUtils sTestUtils;
 
@@ -160,6 +161,38 @@
         assertThat(cacheInfo).doesNotContain("name=\"com.android.runtime\"");
     }
 
+    @Test
+    public void verifyCompilationOsMode() throws Exception {
+        sTestUtils.removeCompilationLogToAvoidBackoff();
+        long timeMs = getCurrentTimeMs();
+        getDevice().executeShellV2Command(ODREFRESH_BIN + " --compilation-os-mode --compile");
+
+        // odrefresh should unconditionally compile everything in Compilation OS.
+        assertArtifactsModifiedAfter(sZygoteArtifacts, timeMs);
+        assertArtifactsModifiedAfter(sSystemServerArtifacts, timeMs);
+
+        String cacheInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
+        assertThat(cacheInfo).contains("compilationOsMode=\"true\"");
+        assertThat(cacheInfo).doesNotContain("lastUpdateMillis=");
+
+        // Compilation OS does not write the compilation log to the host.
+        sTestUtils.removeCompilationLogToAvoidBackoff();
+
+        // Simulate the odrefresh invocation on the next boot.
+        timeMs = getCurrentTimeMs();
+        getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+
+        // odrefresh should not re-compile anything regardless of the missing `lastUpdateMillis`
+        // field.
+        assertArtifactsNotModifiedAfter(sZygoteArtifacts, timeMs);
+        assertArtifactsNotModifiedAfter(sSystemServerArtifacts, timeMs);
+
+        // The cache info should be updated.
+        cacheInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
+        assertThat(cacheInfo).doesNotContain("compilationOsMode=\"true\"");
+        assertThat(cacheInfo).contains("lastUpdateMillis=");
+    }
+
     /**
      * Checks the input line by line and replaces all lines that match the regex with the given
      * replacement.