Merge pull request #22016 from BalestraPatrick/create-benchmark-script

diff --git a/benchmark/README.md b/benchmark/README.md
index 2bcc0e2..44ba62b 100644
--- a/benchmark/README.md
+++ b/benchmark/README.md
@@ -132,12 +132,17 @@
 
 The harness generator supports both single and multiple file tests.
 
-To add a new single file test:
+To add a new single file test, execute the following script with the new of the benchmark:
 
+```
+swift-source$ ./swift/benchmark/scripts/create_benchmark.py YourTestNameHere
+```
+
+The script will automatically:
 1.  Add a new Swift file (`YourTestNameHere.swift`), built according to
     the template below, to the `single-source` directory.
-2.  Add the filename of the new Swift file to CMakeLists.txt
-3.  Edit `main.swift`. Import and register your new Swift module.
+2.  Add the filename of the new Swift file to `CMakeLists.txt`.
+3.  Edit `main.swift` by importing and registering your new Swift module.
 
 To add a new multiple file test:
 
diff --git a/benchmark/scripts/Template.swift b/benchmark/scripts/Template.swift
new file mode 100644
index 0000000..00337a1
--- /dev/null
+++ b/benchmark/scripts/Template.swift
@@ -0,0 +1,22 @@
+//===--- {name}.swift -------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
+//
+//===----------------------------------------------------------------------===//
+
+import TestsUtils
+
+public let {name} = [
+  BenchmarkInfo(name: "{name}", runFunction: run_{name}, tags: [.validation, .api]),
+]
+
+@inline(never)
+public func run_{name}(N: Int) {{
+    // TODO
+}}
\ No newline at end of file
diff --git a/benchmark/scripts/create_benchmark.py b/benchmark/scripts/create_benchmark.py
new file mode 100755
index 0000000..2e2a478
--- /dev/null
+++ b/benchmark/scripts/create_benchmark.py
@@ -0,0 +1,159 @@
+#!/usr/bin/env python
+
+import argparse
+import os
+import re
+
+
+def main():
+    p = argparse.ArgumentParser()
+    p.add_argument('name', help='The name of the new benchmark to be created')
+    args = p.parse_args()
+
+    # adds benchmark to `CMakeLists.txt`
+    update_cmakelists(args.name)
+    # create benchmark Swift file
+    create_benchmark_file(args.name)
+    # imports the benchmark module in `main.swift`
+    add_import_benchmark(args.name)
+    # registers the benchmark with the driver in `main.swift`
+    add_register_benchmark(args.name)
+
+
+def update_cmakelists(name):
+    """Adds a new entry to the `CMakeLists.txt` file with the given
+    benchmark name.
+    """
+    relative_path = create_relative_path('../CMakeLists.txt')
+
+    file_contents = []
+    with open(relative_path, 'r') as f:
+        file_contents = f.readlines()
+
+    file_new_contents = insert_line_alphabetically(
+        name,
+        '    single-source/' + name + '\n',
+        file_contents,
+        r"    single-source\/([a-zA-Z]+)"
+    )
+    with open(relative_path, 'w') as f:
+        for line in file_new_contents:
+            f.write(line)
+
+
+def create_benchmark_file(name):
+    """Creates a new Swift file with the given name based on the template
+    and places it in the `single-source` directory.
+    """
+
+    template_path = create_relative_path('Template.swift')
+    benchmark_template = ''
+    with open(template_path, 'r') as f:
+        benchmark_template = ''.join(f.readlines())
+
+    # fill in template with benchmark name.
+    formatted_template = benchmark_template.format(name=name)
+
+    relative_path = create_relative_path('../single-source/')
+    source_file_path = os.path.join(relative_path, name + '.swift')
+    with open(source_file_path, 'w') as f:
+        f.write(formatted_template)
+
+
+def add_import_benchmark(name):
+    """Adds an `import` statement to the `main.swift` file for the new 
+    benchmark.
+    """
+    relative_path = create_relative_path('../utils/main.swift')
+
+    # read current contents into an array
+    file_contents = []
+    with open(relative_path, 'r') as f:
+        file_contents = f.readlines()
+
+    # the test dependencies are placed before all benchmarks, so we have to 
+    # insert the benchmark in the right alphabetical order after we have seen
+    # all test dependencies.
+    read_test_dependencies = False
+    previous_benchmark_name = None
+    file_new_contents = []
+    for line in file_contents:
+        # check if this line is a definition of a benchmark and get its name
+        match = re.search(r"import ([a-zA-Z]+)", line)
+        if match and match.group(1):
+            benchmark_name = match.group(1)
+            # find where to insert the new benchmark in the right alphabetical 
+            # order.
+            if (name < benchmark_name and previous_benchmark_name is None or
+                    name < benchmark_name and name > previous_benchmark_name):
+                if read_test_dependencies:
+                    file_new_contents.append('import ' + name + '\n' + line)
+                else:
+                    # all test dependencies are first specified, so from now 
+                    # on we can look where to insert the new benchmark.
+                    read_test_dependencies = True
+                    file_new_contents.append(line)
+            else:
+                file_new_contents.append(line)    
+            previous_benchmark_name = benchmark_name
+        else:
+            file_new_contents.append(line)
+    with open(relative_path, 'w') as f:
+        for line in file_new_contents:
+            f.write(line)
+
+
+def add_register_benchmark(name):
+    """Adds an `import` statement to the `main.swift` file for the new
+    benchmark.
+    """
+    relative_path = create_relative_path('../utils/main.swift')
+
+    file_contents = []
+    with open(relative_path, 'r') as f:
+        file_contents = f.readlines()
+
+    file_new_contents = insert_line_alphabetically(
+        name,
+        'registerBenchmark(' + name + ')\n',
+        file_contents, 
+        r"registerBenchmark\(([a-zA-Z]+)\)"
+    )
+    with open(relative_path, 'w') as f:
+        for line in file_new_contents:
+            f.write(line)
+
+
+def insert_line_alphabetically(name, new_line, lines, regex):
+    """Iterates through the given lines and executes the regex on each line to
+    find where the new benchmark should be inserted with the given `new_line`.
+    """
+    # the name of the previous seen benchmark in order to insert the new
+    # one at the correct position 
+    previous_benchmark_name = None
+    # the new contents of the file
+    updated_lines = []
+    for line in lines:
+        # apply regex and get name of benchmark on this line
+        match = re.search(regex, line)
+        if match and match.group(1):
+            benchmark_name = match.group(1)
+            # check if we're at the line where we have to insert the new
+            # benchmark in the correct alphabetical order
+            if (name < benchmark_name and previous_benchmark_name is None or 
+                    name < benchmark_name and name > previous_benchmark_name):
+                updated_lines.append(new_line + line)
+            else:
+                updated_lines.append(line)    
+            previous_benchmark_name = benchmark_name
+        else:
+            updated_lines.append(line)
+    return updated_lines
+
+
+def create_relative_path(file_path):
+    return os.path.join(os.path.dirname(__file__), file_path)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/benchmark/utils/main.swift b/benchmark/utils/main.swift
index 459e5bf..b6b9d95 100644
--- a/benchmark/utils/main.swift
+++ b/benchmark/utils/main.swift
@@ -215,6 +215,7 @@
 registerBenchmark(CharacterPropertiesStashedMemo)
 registerBenchmark(CharacterPropertiesPrecomputed)
 registerBenchmark(Chars)
+registerBenchmark(Codable)
 registerBenchmark(Combos)
 registerBenchmark(CountAlgo)
 registerBenchmark(ClassArrayGetter)
@@ -255,7 +256,6 @@
 registerBenchmark(InsertCharacter)
 registerBenchmark(IntegrateTest)
 registerBenchmark(IterateData)
-registerBenchmark(Codable)
 registerBenchmark(Join)
 registerBenchmark(LazyFilter)
 registerBenchmark(LinkedList)