Add a test target with sharding support to the CMake build.

Two major things going on here:
- One target is created for each file in test-data.sha1 (testdata_N),
  and the testdata target now depends on all testdata_N targets. The
  testdata build rule can now run with as many jobs as there are
  input files to speed up test data download.
- GTest sharding support has been added to the tests via a runtests
  custom build target. First, the number of processors is detected,
  and then a custom target is created for each processor (test_N).
  Once each test_N target is created, the runtests custom target
  is created, and then made to depend on each test_N target. When
  CMake is unable to detect the number of processors 10 test targets
  are created. Each custom target then sets the GTEST_SHARD_INDEX and
  GTEST_TOTAL_SHARDS environment variables, allowing GTest to handle
  sharding internally.

BUG=aomedia:76,aomedia:469

Change-Id: Ib6b7974932396fbf44b735d37155fa57561027ab
diff --git a/test/test.cmake b/test/test.cmake
index 361d9f7..e4fc861 100644
--- a/test/test.cmake
+++ b/test/test.cmake
@@ -8,8 +8,12 @@
 ## Media Patent License 1.0 was not distributed with this source code in the
 ## PATENTS file, you can obtain it at www.aomedia.org/license/patent.
 ##
+include(ProcessorCount)
+
 include("${AOM_ROOT}/test/test_data_util.cmake")
 
+set(AOM_UNIT_TEST_DATA_LIST_FILE "${AOM_ROOT}/test/test-data.sha1")
+
 set(AOM_UNIT_TEST_WRAPPER_SOURCES
     "${AOM_CONFIG_DIR}/usage_exit.c"
     "${AOM_ROOT}/test/test_libaom.cc")
@@ -316,10 +320,52 @@
                                     "AOM_UNIT_TEST_COMMON_INTRIN_NEON")
   endif ()
 
-  add_custom_target(testdata
-                    COMMAND ${CMAKE_COMMAND}
+  make_test_data_lists("${AOM_UNIT_TEST_DATA_LIST_FILE}"
+                       test_files test_file_checksums)
+  list(LENGTH test_files num_test_files)
+  list(LENGTH test_file_checksums num_test_file_checksums)
+
+  math(EXPR max_file_index "${num_test_files} - 1")
+  foreach (test_index RANGE ${max_file_index})
+    list(GET test_files ${test_index} test_file)
+    list(GET test_file_checksums ${test_index} test_file_checksum)
+    add_custom_target(testdata_${test_index}
+                      COMMAND ${CMAKE_COMMAND}
+                        -DAOM_CONFIG_DIR="${AOM_CONFIG_DIR}"
+                        -DAOM_ROOT="${AOM_ROOT}"
+                        -DAOM_TEST_FILE="${test_file}"
+                        -DAOM_TEST_CHECKSUM=${test_file_checksum}
+                        -P "${AOM_ROOT}/test/test_data_download_worker.cmake")
+    set(testdata_targets ${testdata_targets} testdata_${test_index})
+  endforeach ()
+
+  # Create a custom build target for running each test data download target.
+  add_custom_target(testdata)
+  add_dependencies(testdata ${testdata_targets})
+
+  # Pick a reasonable number of targets (this controls parallelization).
+  ProcessorCount(num_test_targets)
+  if (num_test_targets EQUAL 0)
+    # Just default to 10 targets when there's no processor count available.
+    set(num_test_targets 10)
+  endif ()
+
+  # TODO(tomfinegan): This needs some work for MSVC and Xcode. Executable suffix
+  # and config based executable output paths are the obvious issues.
+  math(EXPR max_shard_index "${num_test_targets} - 1")
+  foreach (shard_index RANGE ${max_shard_index})
+    set(test_name "test_${shard_index}")
+    add_custom_target(${test_name}
+                      COMMAND ${CMAKE_COMMAND}
                       -DAOM_CONFIG_DIR="${AOM_CONFIG_DIR}"
                       -DAOM_ROOT="${AOM_ROOT}"
-                      -P "${AOM_ROOT}/test/test_worker.cmake"
-                    SOURCES ${AOM_TEST_DATA_LIST})
+                      -DAOM_TEST_TARGET=test_libaom
+                      -DGTEST_SHARD_INDEX=${shard_index}
+                      -DGTEST_TOTAL_SHARDS=${num_test_targets}
+                      -P "${AOM_ROOT}/test/test_runner.cmake"
+                      DEPENDS testdata test_libaom)
+    set(test_targets ${test_targets} ${test_name})
+  endforeach ()
+  add_custom_target(runtests)
+  add_dependencies(runtests ${test_targets})
 endfunction ()
diff --git a/test/test_data_download_worker.cmake b/test/test_data_download_worker.cmake
new file mode 100644
index 0000000..d7bf99e
--- /dev/null
+++ b/test/test_data_download_worker.cmake
@@ -0,0 +1,40 @@
+##
+## Copyright (c) 2017, Alliance for Open Media. All rights reserved
+##
+## This source code is subject to the terms of the BSD 2 Clause License and
+## the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
+## was not distributed with this source code in the LICENSE file, you can
+## obtain it at www.aomedia.org/license/software. If the Alliance for Open
+## Media Patent License 1.0 was not distributed with this source code in the
+## PATENTS file, you can obtain it at www.aomedia.org/license/patent.
+##
+include("${AOM_ROOT}/test/test_data_util.cmake")
+
+if (NOT AOM_ROOT OR NOT AOM_CONFIG_DIR OR NOT AOM_TEST_FILE
+    OR NOT AOM_TEST_CHECKSUM)
+  message(FATAL_ERROR
+          "AOM_ROOT, AOM_CONFIG_DIR, AOM_TEST_FILE and AOM_TEST_CHECKSUM must be
+          defined.")
+endif ()
+
+set(AOM_TEST_DATA_URL
+    "https://storage.googleapis.com/downloads.webmproject.org/test_data/libvpx")
+set(AOM_TEST_DATA_PATH "$ENV{LIBAOM_TEST_DATA_PATH}")
+
+if ("${AOM_TEST_DATA_PATH}" STREQUAL "")
+  message(WARNING "Writing test data to ${AOM_CONFIG_DIR}, set "
+          "$LIBAOM_TEST_DATA_PATH in your environment to avoid this warning.")
+  set(AOM_TEST_DATA_PATH "${AOM_CONFIG_DIR}")
+endif ()
+
+if (NOT EXISTS "${AOM_TEST_DATA_PATH}")
+  file(MAKE_DIRECTORY "${AOM_TEST_DATA_PATH}")
+endif ()
+
+expand_test_file_paths("AOM_TEST_FILE" "${AOM_TEST_DATA_PATH}" "filepath")
+expand_test_file_paths("AOM_TEST_FILE" "${AOM_TEST_DATA_URL}" "url")
+
+check_file("${filepath}" "${AOM_TEST_CHECKSUM}" "needs_download")
+if (needs_download)
+  download_test_file("${url}" "${AOM_TEST_CHECKSUM}" "${filepath}")
+endif ()
diff --git a/test/test_data_util.cmake b/test/test_data_util.cmake
index f096e4e..e464104 100644
--- a/test/test_data_util.cmake
+++ b/test/test_data_util.cmake
@@ -11,15 +11,14 @@
 
 # Parses test/test-data.sha1 and writes captured file names and checksums to
 # $out_files and $out_checksums as lists.
-function (make_test_data_lists out_files out_checksums)
-  if (NOT AOM_TEST_DATA_LIST OR NOT EXISTS "${AOM_TEST_DATA_LIST}")
-    message(FATAL_ERROR "AOM_TEST_DATA_LIST (${AOM_TEST_DATA_LIST}) missing or "
-            "variable empty.")
+function (make_test_data_lists test_data_file out_files out_checksums)
+  if (NOT test_data_file OR NOT EXISTS "${test_data_file}")
+    message(FATAL_ERROR "Test info file missing or empty (${test_data_file})")
   endif ()
 
-  # Read test-data.sha1 into $files_and_checksums. $files_and_checksums becomes
-  # a list with an entry for each line from $AOM_TEST_DATA_LIST.
-  file(STRINGS "${AOM_TEST_DATA_LIST}" files_and_checksums)
+  # Read $test_data_file into $files_and_checksums. $files_and_checksums becomes
+  # a list with an entry for each line from $test_data_file.
+  file(STRINGS "${test_data_file}" files_and_checksums)
 
   # Iterate over the list of lines and split it into $checksums and $filenames.
   foreach (line ${files_and_checksums})
@@ -33,8 +32,10 @@
     set(filenames ${filenames} ${filename})
   endforeach ()
 
-  if (NOT checksums OR NOT filenames)
-    message(FATAL_ERROR "Parsing of ${AOM_TEST_DATA_LIST} failed.")
+  list(LENGTH filenames num_files)
+  list(LENGTH checksums num_checksums)
+  if (NOT checksums OR NOT filenames OR NOT num_files EQUAL num_checksums)
+    message(FATAL_ERROR "Parsing of ${test_data_file} failed.")
   endif ()
 
   set(${out_checksums} ${checksums} PARENT_SCOPE)
@@ -62,7 +63,9 @@
     unset(${out_needs_update} PARENT_SCOPE)
   else ()
     set(${out_needs_update} 1 PARENT_SCOPE)
+    return ()
   endif ()
+  message("${local_path} up to date.")
 endfunction ()
 
 # Downloads data from $file_url, confirms that $file_checksum matches, and
diff --git a/test/test_runner.cmake b/test/test_runner.cmake
new file mode 100644
index 0000000..b8176d7
--- /dev/null
+++ b/test/test_runner.cmake
@@ -0,0 +1,20 @@
+##
+## Copyright (c) 2017, Alliance for Open Media. All rights reserved
+##
+## This source code is subject to the terms of the BSD 2 Clause License and
+## the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
+## was not distributed with this source code in the LICENSE file, you can
+## obtain it at www.aomedia.org/license/software. If the Alliance for Open
+## Media Patent License 1.0 was not distributed with this source code in the
+## PATENTS file, you can obtain it at www.aomedia.org/license/patent.
+##
+if (NOT AOM_ROOT OR NOT AOM_CONFIG_DIR OR NOT AOM_TEST_TARGET
+    OR NOT GTEST_TOTAL_SHARDS OR "${GTEST_SHARD_INDEX}" STREQUAL "")
+  message(FATAL_ERROR
+          "The variables AOM_ROOT AOM_CONFIG_DIR AOM_TEST_TARGET
+          GTEST_SHARD_INDEX and GTEST_TOTAL_SHARDS must be defined.")
+endif ()
+
+set($ENV{GTEST_SHARD_INDEX} ${GTEST_SHARD_INDEX})
+set($ENV{GTEST_TOTAL_SHARDS} ${GTEST_TOTAL_SHARDS})
+execute_process(COMMAND ${AOM_CONFIG_DIR}/${AOM_TEST_TARGET})
diff --git a/test/test_worker.cmake b/test/test_worker.cmake
deleted file mode 100644
index a57cf1a..0000000
--- a/test/test_worker.cmake
+++ /dev/null
@@ -1,49 +0,0 @@
-##
-## Copyright (c) 2017, Alliance for Open Media. All rights reserved
-##
-## This source code is subject to the terms of the BSD 2 Clause License and
-## the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
-## was not distributed with this source code in the LICENSE file, you can
-## obtain it at www.aomedia.org/license/software. If the Alliance for Open
-## Media Patent License 1.0 was not distributed with this source code in the
-## PATENTS file, you can obtain it at www.aomedia.org/license/patent.
-##
-if (NOT AOM_ROOT OR NOT AOM_CONFIG_DIR)
-  message(FATAL_ERROR "AOM_ROOT AND AOM_CONFIG_DIR must be defined.")
-endif ()
-
-set(AOM_TEST_DATA_LIST "${AOM_ROOT}/test/test-data.sha1")
-set(AOM_TEST_DATA_URL "http://downloads.webmproject.org/test_data/libvpx")
-set(AOM_TEST_DATA_PATH "$ENV{LIBAOM_TEST_DATA_PATH}")
-
-include("${AOM_ROOT}/test/test_data_util.cmake")
-
-if ("${AOM_TEST_DATA_PATH}" STREQUAL "")
-  message(WARNING "Writing test data to ${AOM_CONFIG_DIR}, set "
-          "$LIBAOM_TEST_DATA_PATH in your environment to avoid this warning.")
-  set(AOM_TEST_DATA_PATH "${AOM_CONFIG_DIR}")
-endif ()
-
-if (NOT EXISTS "${AOM_TEST_DATA_PATH}")
-  file(MAKE_DIRECTORY "${AOM_TEST_DATA_PATH}")
-endif ()
-
-make_test_data_lists("AOM_TEST_DATA_FILES" "AOM_TEST_DATA_CHECKSUMS")
-expand_test_file_paths("AOM_TEST_DATA_FILES" "${AOM_TEST_DATA_PATH}"
-                       "AOM_TEST_DATA_FILE_PATHS")
-expand_test_file_paths("AOM_TEST_DATA_FILES" "${AOM_TEST_DATA_URL}"
-                       "AOM_TEST_DATA_URLS")
-list(LENGTH AOM_TEST_DATA_FILES num_files)
-math(EXPR num_files "${num_files} - 1")
-
-foreach (file_num RANGE ${num_files})
-  list(GET AOM_TEST_DATA_FILES ${file_num} filename)
-  list(GET AOM_TEST_DATA_CHECKSUMS ${file_num} checksum)
-  list(GET AOM_TEST_DATA_FILE_PATHS ${file_num} filepath)
-  list(GET AOM_TEST_DATA_URLS ${file_num} url)
-
-  check_file("${filepath}" "${checksum}" "needs_download")
-  if (needs_download)
-    download_test_file("${url}" "${checksum}" "${filepath}")
-  endif ()
-endforeach ()