Getting most of the tests running on Windows.

This adds proper iree_cc_test and iree_lit_test execution via cmake test on Windows. A handful of tests are failing due to file IO issues that will be fixed in follow-on changes.

Closes https://github.com/google/iree/pull/899

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/iree/pull/899 from google:benvanik-win32-test 1ef44695c721f0edc82e49aec3737e18356bbbe8
PiperOrigin-RevId: 298599246
diff --git a/build_tools/cmake/iree_cc_test.cmake b/build_tools/cmake/iree_cc_test.cmake
index 3166c7f..eeaf651 100644
--- a/build_tools/cmake/iree_cc_test.cmake
+++ b/build_tools/cmake/iree_cc_test.cmake
@@ -103,7 +103,21 @@
   set_property(TARGET ${_NAME} PROPERTY CXX_STANDARD ${IREE_CXX_STANDARD})
   set_property(TARGET ${_NAME} PROPERTY CXX_STANDARD_REQUIRED ON)
 
-  # We run all our tests through a custom test runner to allow setup and teardown.
-  add_test(NAME ${_NAME} COMMAND ${CMAKE_SOURCE_DIR}/build_tools/cmake/run_test.sh "${CMAKE_CURRENT_BINARY_DIR}/${_NAME}")
-  set_property(TEST ${_NAME} PROPERTY ENVIRONMENT "TEST_TMPDIR=${_NAME}_test_tmpdir")
+  # We run all our tests through a custom test runner to allow temp directory
+  # cleanup upon test completion.
+  add_test(
+    NAME
+      ${_NAME}
+    COMMAND
+      "${CMAKE_SOURCE_DIR}/build_tools/cmake/run_test.${IREE_HOST_SCRIPT_EXT}"
+      "$<TARGET_FILE:${_NAME}>"
+    WORKING_DIRECTORY
+      "${CMAKE_BINARY_DIR}"
+    )
+  set_property(
+    TEST
+      ${_NAME}
+    PROPERTY
+      ENVIRONMENT "TEST_TMPDIR=${_NAME}_test_tmpdir"
+  )
 endfunction()
diff --git a/build_tools/cmake/iree_lit_test.cmake b/build_tools/cmake/iree_lit_test.cmake
index 6620769..0874119 100644
--- a/build_tools/cmake/iree_lit_test.cmake
+++ b/build_tools/cmake/iree_lit_test.cmake
@@ -45,13 +45,38 @@
 
   get_filename_component(_TEST_FILE_PATH ${_RULE_TEST_FILE} ABSOLUTE)
 
+  set(_DATA_DEP_PATHS)
+  foreach(_DATA_DEP ${_RULE_DATA})
+    string(REPLACE "::" "_" _DATA_DEP_NAME ${_DATA_DEP})
+    # TODO(*): iree_sh_binary so we can avoid this.
+    if("${_DATA_DEP_NAME}" STREQUAL "iree_tools_IreeFileCheck")
+      list(APPEND _DATA_DEP_PATHS "${CMAKE_SOURCE_DIR}/iree/tools/IreeFileCheck.bat")
+    else()
+      list(APPEND _DATA_DEP_PATHS $<TARGET_FILE:${_DATA_DEP_NAME}>)
+    endif()
+  endforeach(_DATA_DEP)
+
+  # We run all our tests through a custom test runner to allow setup and teardown.
   add_test(
-    NAME ${_NAME}
-    # We run all our tests through a custom test runner to allow setup and teardown.
-    COMMAND ${CMAKE_SOURCE_DIR}/build_tools/cmake/run_test.sh ${CMAKE_SOURCE_DIR}/iree/tools/run_lit.sh ${_TEST_FILE_PATH}
-    WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" # Make sure the lit runner can find all the binaries
+    NAME
+      ${_NAME}
+    COMMAND
+      "${CMAKE_SOURCE_DIR}/build_tools/cmake/run_test.${IREE_HOST_SCRIPT_EXT}"
+      "${CMAKE_SOURCE_DIR}/iree/tools/run_lit.${IREE_HOST_SCRIPT_EXT}"
+      ${_TEST_FILE_PATH}
+      ${_DATA_DEP_PATHS}
+    WORKING_DIRECTORY
+      "${CMAKE_BINARY_DIR}"
   )
-  set_property(TEST ${_NAME} PROPERTY ENVIRONMENT "TEST_TMPDIR=${_NAME}_test_tmpdir")
+  set_property(
+    TEST
+      ${_NAME}
+    PROPERTY
+      ENVIRONMENT
+        "TEST_TMPDIR=${_NAME}_test_tmpdir"
+      REQUIRED_FILES
+        "${_TEST_FILE_PATH}"
+  )
   # TODO(gcmn): Figure out how to indicate a dependency on _RULE_DATA being built
 endfunction()
 
diff --git a/build_tools/cmake/iree_macros.cmake b/build_tools/cmake/iree_macros.cmake
index 4afc54e..683460a 100644
--- a/build_tools/cmake/iree_macros.cmake
+++ b/build_tools/cmake/iree_macros.cmake
@@ -15,6 +15,16 @@
 include(CMakeParseArguments)
 
 #-------------------------------------------------------------------------------
+# Missing CMake Variables
+#-------------------------------------------------------------------------------
+
+if(${CMAKE_HOST_SYSTEM_NAME} STREQUAL "Windows")
+  set(IREE_HOST_SCRIPT_EXT "bat")
+else()
+  set(IREE_HOST_SCRIPT_EXT "sh")
+endif()
+
+#-------------------------------------------------------------------------------
 # Packages and Paths
 #-------------------------------------------------------------------------------
 
diff --git a/build_tools/cmake/run_test.bat b/build_tools/cmake/run_test.bat
new file mode 100644
index 0000000..61f5fe0
--- /dev/null
+++ b/build_tools/cmake/run_test.bat
@@ -0,0 +1,18 @@
+@ECHO OFF
+REM Copyright 2020 Google LLC
+REM
+REM Licensed under the Apache License, Version 2.0 (the "License");
+REM you may not use this file except in compliance with the License.
+REM You may obtain a copy of the License at
+REM
+REM      https://www.apache.org/licenses/LICENSE-2.0
+REM
+REM Unless required by applicable law or agreed to in writing, software
+REM distributed under the License is distributed on an "AS IS" BASIS,
+REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+REM See the License for the specific language governing permissions and
+REM limitations under the License.
+
+SET RUNNER_PATH=%~dp0
+powershell.exe -NoProfile -File "%RUNNER_PATH%\run_test.ps1" %*
+EXIT /B %ERRORLEVEL%
diff --git a/build_tools/cmake/run_test.ps1 b/build_tools/cmake/run_test.ps1
new file mode 100644
index 0000000..eab362d
--- /dev/null
+++ b/build_tools/cmake/run_test.ps1
@@ -0,0 +1,76 @@
+# Copyright 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
+#
+#      https://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.
+
+param(
+  [Parameter(Position=0, Mandatory)]
+  [ValidateNotNullOrEmpty()]
+  [string]
+    $test_binary,
+  [Parameter(Position=1, ValueFromRemainingArguments=$true)]
+  [string[]]
+    $test_args = @()
+)
+
+# NOTE: to debug first run `$DebugPreference = 'Continue'` in your shell.
+# $DebugPreference = "Continue"
+
+# Create/cleanup the test output directory (where gtest files are written, etc).
+$test_tmpdir = $env:TEST_TMPDIR
+if ($null -eq $test_tmpdir) {
+  Write-Error "TEST_TMPDIR environment variable not set" -Category InvalidArgument
+  Get-ChildItem env:
+  exit 1
+}
+Write-Debug "Preparing test output path $test_tmpdir..."
+if (Test-Path $test_tmpdir) {
+  Write-Debug "Removing existing folder at $test_tmpdir"
+  Remove-Item $test_tmpdir -Recurse -Force
+}
+New-Item -Path $test_tmpdir -ItemType Directory -Force | Out-Null
+Write-Debug "Created new folder at $test_tmpdir"
+trap {
+  if (Test-Path $test_tmpdir) {
+    Write-Debug "Cleaning up $test_tmpdir on error..."
+    Remove-Item $test_tmpdir -Recurse -Force
+  }
+  Write-Error $_
+  exit 1
+}
+
+# Run the test executable with all arguments we were passed.
+Write-Host -ForegroundColor Blue "Running test:"
+Write-Host -ForegroundColor Yellow "$test_binary $test_args"
+$process = $null
+if ($test_args.Count -gt 0) {
+  $process = Start-Process -FilePath $test_binary -ArgumentList $test_args -NoNewWindow -PassThru
+} else {
+  $process = Start-Process -FilePath $test_binary -NoNewWindow -PassThru
+}
+# HACK: Start-Process is broken... wow.
+# https://stackoverflow.com/questions/10262231/obtaining-exitcode-using-start-process-and-waitforexit-instead-of-wait
+$handle = $process.Handle
+$exitcode = 1
+$timeout_millis = 120 * 1000
+if ($process.WaitForExit($timeout_millis) -eq $false) {
+  Write-Error "Test timed out after $timeout_millis millis"
+} else {
+  $exitcode = $process.ExitCode
+  Write-Debug "Test returned in time with exit code $($process.ExitCode)"
+}
+
+# Cleanup test tempdir.
+Write-Debug "Cleaning up $test_tmpdir..."
+Remove-Item $test_tmpdir -Recurse -Force
+Write-Debug "Test exited with $exitcode"
+exit $exitcode
diff --git a/iree/base/internal/file_handle_win32.cc b/iree/base/internal/file_handle_win32.cc
index 4517943..ae69b60 100644
--- a/iree/base/internal/file_handle_win32.cc
+++ b/iree/base/internal/file_handle_win32.cc
@@ -47,6 +47,22 @@
   return absl::make_unique<FileHandle>(handle, file_size);
 }
 
+// static
+StatusOr<std::unique_ptr<FileHandle>> FileHandle::OpenWrite(std::string path,
+                                                            DWORD file_flags) {
+  HANDLE handle = ::CreateFileA(
+      /*lpFileName=*/path.c_str(), /*dwDesiredAccess=*/GENERIC_WRITE,
+      /*dwShareMode=*/FILE_SHARE_DELETE, /*lpSecurityAttributes=*/nullptr,
+      /*dwCreationDisposition=*/CREATE_ALWAYS,
+      /*dwFlagsAndAttributes=*/FILE_ATTRIBUTE_NORMAL | file_flags,
+      /*hTemplateFile=*/nullptr);
+  if (handle == INVALID_HANDLE_VALUE) {
+    return Win32ErrorToCanonicalStatusBuilder(GetLastError(), IREE_LOC)
+           << "Unable to open file " << path;
+  }
+  return absl::make_unique<FileHandle>(handle, 0);
+}
+
 FileHandle::~FileHandle() { ::CloseHandle(handle_); }
 
 }  // namespace iree
diff --git a/iree/base/internal/file_handle_win32.h b/iree/base/internal/file_handle_win32.h
index 297dd20..70e5113 100644
--- a/iree/base/internal/file_handle_win32.h
+++ b/iree/base/internal/file_handle_win32.h
@@ -32,6 +32,8 @@
  public:
   static StatusOr<std::unique_ptr<FileHandle>> OpenRead(std::string path,
                                                         DWORD file_flags);
+  static StatusOr<std::unique_ptr<FileHandle>> OpenWrite(std::string path,
+                                                         DWORD file_flags);
 
   FileHandle(HANDLE handle, size_t size) : handle_(handle), size_(size) {}
   ~FileHandle();
diff --git a/iree/base/internal/file_io_win32.cc b/iree/base/internal/file_io_win32.cc
index 8486b94..68e044b 100644
--- a/iree/base/internal/file_io_win32.cc
+++ b/iree/base/internal/file_io_win32.cc
@@ -53,8 +53,14 @@
 }
 
 Status SetFileContents(const std::string& path, const std::string& content) {
-  return UnimplementedErrorBuilder(IREE_LOC)
-         << "SetFileContents unimplemented on Windows";
+  ASSIGN_OR_RETURN(auto file, FileHandle::OpenWrite(std::move(path), 0));
+  if (::WriteFile(file->handle(), content.data(), content.size(), NULL, NULL) ==
+      FALSE) {
+    return Win32ErrorToCanonicalStatusBuilder(GetLastError(), IREE_LOC)
+           << "Unable to write file span of " << content.size() << " bytes to '"
+           << path << "'";
+  }
+  return OkStatus();
 }
 
 Status DeleteFile(const std::string& path) {
diff --git a/iree/tools/CMakeLists.txt b/iree/tools/CMakeLists.txt
index a8b3873..a1bfc73 100644
--- a/iree/tools/CMakeLists.txt
+++ b/iree/tools/CMakeLists.txt
@@ -305,6 +305,9 @@
       iree::tools::iree_translate_library
   )
 
+  # TODO(*): define these with a rule instead (like iree_sh_binary).
+  # We want them to have the same package namespacing so we can reference them
+  # as if they were binaries (iree::tools::IreeFileCheck, etc).
   if(${IREE_BUILD_TESTS})
     add_custom_target(IreeFileCheck ALL
       COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_SOURCE_DIR}/IreeFileCheck.sh IreeFileCheck
diff --git a/iree/tools/IreeFileCheck.bat b/iree/tools/IreeFileCheck.bat
new file mode 100644
index 0000000..dbd4d5f
--- /dev/null
+++ b/iree/tools/IreeFileCheck.bat
@@ -0,0 +1,17 @@
+@ECHO OFF
+REM Copyright 2020 Google LLC
+REM
+REM Licensed under the Apache License, Version 2.0 (the "License");
+REM you may not use this file except in compliance with the License.
+REM You may obtain a copy of the License at
+REM
+REM      https://www.apache.org/licenses/LICENSE-2.0
+REM
+REM Unless required by applicable law or agreed to in writing, software
+REM distributed under the License is distributed on an "AS IS" BASIS,
+REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+REM See the License for the specific language governing permissions and
+REM limitations under the License.
+
+FileCheck --enable-var-scope --dump-input=fail %*
+EXIT /B %ERRORLEVEL%
diff --git a/iree/tools/run_lit.bat b/iree/tools/run_lit.bat
new file mode 100644
index 0000000..abfb082
--- /dev/null
+++ b/iree/tools/run_lit.bat
@@ -0,0 +1,18 @@
+@ECHO OFF
+REM Copyright 2020 Google LLC
+REM
+REM Licensed under the Apache License, Version 2.0 (the "License");
+REM you may not use this file except in compliance with the License.
+REM You may obtain a copy of the License at
+REM
+REM      https://www.apache.org/licenses/LICENSE-2.0
+REM
+REM Unless required by applicable law or agreed to in writing, software
+REM distributed under the License is distributed on an "AS IS" BASIS,
+REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+REM See the License for the specific language governing permissions and
+REM limitations under the License.
+
+SET RUNNER_PATH=%~dp0
+powershell.exe -NoProfile -File "%RUNNER_PATH%\run_lit.ps1" %*
+EXIT /B %ERRORLEVEL%
diff --git a/iree/tools/run_lit.ps1 b/iree/tools/run_lit.ps1
new file mode 100644
index 0000000..fa73b2a
--- /dev/null
+++ b/iree/tools/run_lit.ps1
@@ -0,0 +1,63 @@
+# Copyright 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
+#
+#      https://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.
+
+param(
+  [Parameter(Position=0, Mandatory)]
+  [ValidateNotNullOrEmpty()]
+  [string]
+    $test_file,
+  [Parameter(Position=1, ValueFromRemainingArguments=$true)]
+  [string[]]
+    $test_data = @()
+)
+
+# NOTE: to debug first run `$DebugPreference = 'Continue'` in your shell.
+# $DebugPreference = "Continue"
+
+trap {
+  Write-Error $_
+  exit 1
+}
+
+# Get all of the directories we'll want to put on our path for the test.
+$test_dirs = [System.Collections.ArrayList]@()
+foreach ($test_path in $test_data) {
+  $test_dir = Split-Path -Path $test_path -Parent
+  $test_dirs.Add($test_dir) | Out-Null
+}
+Write-Debug "Test data directories: $test_dirs"
+$test_dirs.AddRange($env:Path.Split(";"))
+$env:Path = $test_dirs -join ";"
+Write-Debug "Test PATH:"
+Write-Debug "$env:PATH"
+
+$test_lines = Get-Content -Path $test_file
+foreach ($test_line in $test_lines) {
+  if (!$test_line.StartsWith("// RUN:")) {
+    continue
+  }
+  $test_line = $test_line.Substring("// RUN: ".Length)
+  $test_line = $test_line -replace "%s", $test_file
+  Write-Host -ForegroundColor Blue "Running test command:"
+  Write-Host -ForegroundColor Yellow "$test_line"
+  & "bash.exe" -c $test_line | Out-Default
+  if ($LASTEXITCODE -gt 0) {
+    Write-Host -ForegroundColor Red "Test failed with $LASTEXITCODE, command:"
+    Write-Host -ForegroundColor Yellow "$test_line"
+    exit $LASTEXITCODE
+  }
+}
+
+Write-Debug "All run commands completed successfully"
+exit 0