diff --git a/.github/workflows/comment_bot.yml b/.github/workflows/comment_bot.yml index 9e103003eeed..6ca095328af1 100644 --- a/.github/workflows/comment_bot.yml +++ b/.github/workflows/comment_bot.yml @@ -33,13 +33,13 @@ jobs: - name: Checkout Arrow uses: actions/checkout@v2 with: - path: arrow + repository: apache/arrow - name: Set up Python uses: actions/setup-python@v2 with: python-version: 3.8 - name: Install Archery and Crossbow dependencies - run: pip install -e arrow/dev/archery[bot] + run: pip install -e dev/archery[bot] - name: Handle Github comment event env: ARROW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -49,78 +49,6 @@ jobs: --event-name ${{ github.event_name }} \ --event-payload ${{ github.event_path }} - autotune: - name: "Fix all the things" - if: startsWith(github.event.comment.body, '@github-actions autotune') - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: r-lib/actions/pr-fetch@master - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - - name: See what is different - run: | - set -ex - git remote add upstream https://github.com/apache/arrow - git fetch upstream - changed() { - git diff --name-only HEAD..upstream/master | grep -e "$1" >/dev/null 2>&1 - } - if changed '^r/.*\.R$'; then - echo "R_DOCS=true" >> $GITHUB_ENV - fi - if changed 'cmake' || changed 'CMake'; then - echo "CMAKE_FORMAT=true" >> $GITHUB_ENV - fi - if changed '^cpp/src'; then - echo "CLANG_FORMAT_CPP=true" >> $GITHUB_ENV - fi - if changed '^r/src'; then - echo "CLANG_FORMAT_R=true" >> $GITHUB_ENV - fi - - name: Run cmake_format - if: env.CMAKE_FORMAT == 'true' || endsWith(github.event.comment.body, 'everything') - run: | - set -ex - export PATH=/home/runner/.local/bin:$PATH - python3 -m pip install --upgrade pip setuptools wheel - python3 -m pip install -r dev/archery/requirements-lint.txt - python3 run-cmake-format.py - - name: Run clang-format on cpp - if: env.CLANG_FORMAT_CPP == 'true' || endsWith(github.event.comment.body, 'everything') - run: | - . .env # To get the clang version we use - cpp/build-support/run_clang_format.py \ - --clang_format_binary=clang-format-${CLANG_TOOLS} \ - --exclude_glob=cpp/build-support/lint_exclusions.txt \ - --source_dir=cpp/src --quiet --fix - - name: Run clang-format on r - if: env.CLANG_FORMAT_R == 'true' || endsWith(github.event.comment.body, 'everything') - run: | - . .env # To get the clang version we use - cpp/build-support/run_clang_format.py \ - --clang_format_binary=clang-format-${CLANG_TOOLS} \ - --exclude_glob=cpp/build-support/lint_exclusions.txt \ - --source_dir=r/src --quiet --fix - - uses: r-lib/actions/setup-r@v1 - if: env.R_DOCS == 'true' || endsWith(github.event.comment.body, 'everything') - - name: Update R docs - if: env.R_DOCS == 'true' || endsWith(github.event.comment.body, 'everything') - shell: Rscript {0} - run: | - source("ci/etc/rprofile") - install.packages(c("remotes", "roxygen2")) - remotes::install_deps("r") - roxygen2::roxygenize("r") - - name: Commit results - run: | - git config user.name "$(git log -1 --pretty=format:%an)" - git config user.email "$(git log -1 --pretty=format:%ae)" - git commit -a -m 'Autoformat/render all the things [automated commit]' || echo "No changes to commit" - - uses: r-lib/actions/pr-push@master - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - rebase: name: "Rebase" if: startsWith(github.event.comment.body, '@github-actions rebase') diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 545cb97cd2da..548f0ddbb52e 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -27,19 +27,31 @@ env: ARCHERY_DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }} jobs: - lint: - name: Lint C++, Python, R, Rust, Docker, RAT + + rat: + name: Release Audit Tool (RAT) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - name: Checkout Arrow + uses: actions/checkout@v2 + with: + repository: apache/arrow + submodules: true + fetch-depth: 0 + - name: Checkout Arrow Rust + uses: actions/checkout@v2 + with: + path: rust + fetch-depth: 0 - name: Setup Python uses: actions/setup-python@v1 with: python-version: 3.8 - name: Setup Archery - run: pip install -e dev/archery[docker] + run: pip install -e dev/archery[lint] - name: Lint run: archery lint --rat + prettier: name: Use prettier to check formatting of documents runs-on: ubuntu-latest diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 115bfad546d0..cab6dd34caac 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -33,16 +33,13 @@ jobs: repository: apache/arrow submodules: true fetch-depth: 0 - # this is temporary: once rust is removed from `apache/arrow`, we are good to go. - - name: Remove Rust from arrow - run: rm -rf rust/ - name: Checkout Arrow Rust uses: actions/checkout@v2 with: path: rust fetch-depth: 0 - name: Setup Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: 3.8 - name: Setup Archery diff --git a/dev/archery/MANIFEST.in b/dev/archery/MANIFEST.in deleted file mode 100644 index 90fe034c2134..000000000000 --- a/dev/archery/MANIFEST.in +++ /dev/null @@ -1,4 +0,0 @@ -include ../../LICENSE.txt -include ../../NOTICE.txt - -include archery/reports/* diff --git a/dev/archery/archery/__init__.py b/dev/archery/archery/__init__.py deleted file mode 100644 index 13a83393a912..000000000000 --- a/dev/archery/archery/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. diff --git a/dev/archery/archery/benchmark/__init__.py b/dev/archery/archery/benchmark/__init__.py deleted file mode 100644 index 13a83393a912..000000000000 --- a/dev/archery/archery/benchmark/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. diff --git a/dev/archery/archery/benchmark/codec.py b/dev/archery/archery/benchmark/codec.py deleted file mode 100644 index 4157890d13d0..000000000000 --- a/dev/archery/archery/benchmark/codec.py +++ /dev/null @@ -1,97 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - - -import json - -from ..benchmark.core import Benchmark, BenchmarkSuite -from ..benchmark.runner import BenchmarkRunner, StaticBenchmarkRunner -from ..benchmark.compare import BenchmarkComparator - - -class JsonEncoder(json.JSONEncoder): - def default(self, o): - if isinstance(o, Benchmark): - return BenchmarkCodec.encode(o) - - if isinstance(o, BenchmarkSuite): - return BenchmarkSuiteCodec.encode(o) - - if isinstance(o, BenchmarkRunner): - return BenchmarkRunnerCodec.encode(o) - - if isinstance(o, BenchmarkComparator): - return BenchmarkComparatorCodec.encode(o) - - return json.JSONEncoder.default(self, o) - - -class BenchmarkCodec: - @staticmethod - def encode(b): - return { - "name": b.name, - "unit": b.unit, - "less_is_better": b.less_is_better, - "values": b.values, - "time_unit": b.time_unit, - "times": b.times, - "counters": b.counters, - } - - @staticmethod - def decode(dct, **kwargs): - return Benchmark(**dct, **kwargs) - - -class BenchmarkSuiteCodec: - @staticmethod - def encode(bs): - return { - "name": bs.name, - "benchmarks": [BenchmarkCodec.encode(b) for b in bs.benchmarks] - } - - @staticmethod - def decode(dct, **kwargs): - benchmarks = [BenchmarkCodec.decode(b) - for b in dct.pop("benchmarks", [])] - return BenchmarkSuite(benchmarks=benchmarks, **dct, **kwargs) - - -class BenchmarkRunnerCodec: - @staticmethod - def encode(br): - return {"suites": [BenchmarkSuiteCodec.encode(s) for s in br.suites]} - - @staticmethod - def decode(dct, **kwargs): - suites = [BenchmarkSuiteCodec.decode(s) - for s in dct.pop("suites", [])] - return StaticBenchmarkRunner(suites=suites, **dct, **kwargs) - - -class BenchmarkComparatorCodec: - @staticmethod - def encode(bc): - comparator = bc.formatted - - suite_name = bc.suite_name - if suite_name: - comparator["suite"] = suite_name - - return comparator diff --git a/dev/archery/archery/benchmark/compare.py b/dev/archery/archery/benchmark/compare.py deleted file mode 100644 index 622b80179178..000000000000 --- a/dev/archery/archery/benchmark/compare.py +++ /dev/null @@ -1,173 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - - -# Define a global regression threshold as 5%. This is purely subjective and -# flawed. This does not track cumulative regression. -DEFAULT_THRESHOLD = 0.05 - - -def items_per_seconds_fmt(value): - if value < 1000: - return "{} items/sec".format(value) - if value < 1000**2: - return "{:.3f}K items/sec".format(value / 1000) - if value < 1000**3: - return "{:.3f}M items/sec".format(value / 1000**2) - else: - return "{:.3f}G items/sec".format(value / 1000**3) - - -def bytes_per_seconds_fmt(value): - if value < 1024: - return "{} bytes/sec".format(value) - if value < 1024**2: - return "{:.3f} KiB/sec".format(value / 1024) - if value < 1024**3: - return "{:.3f} MiB/sec".format(value / 1024**2) - if value < 1024**4: - return "{:.3f} GiB/sec".format(value / 1024**3) - else: - return "{:.3f} TiB/sec".format(value / 1024**4) - - -def change_fmt(value): - return "{:.3%}".format(value) - - -def formatter_for_unit(unit): - if unit == "bytes_per_second": - return bytes_per_seconds_fmt - elif unit == "items_per_second": - return items_per_seconds_fmt - else: - return lambda x: x - - -class BenchmarkComparator: - """ Compares two benchmarks. - - Encodes the logic of comparing two benchmarks and taking a decision on - if it induce a regression. - """ - - def __init__(self, contender, baseline, threshold=DEFAULT_THRESHOLD, - suite_name=None): - self.contender = contender - self.baseline = baseline - self.threshold = threshold - self.suite_name = suite_name - - @property - def name(self): - return self.baseline.name - - @property - def less_is_better(self): - return self.baseline.less_is_better - - @property - def unit(self): - return self.baseline.unit - - @property - def change(self): - new = self.contender.value - old = self.baseline.value - - if old == 0 and new == 0: - return 0.0 - if old == 0: - return 0.0 - - return float(new - old) / abs(old) - - @property - def confidence(self): - """ Indicate if a comparison of benchmarks should be trusted. """ - return True - - @property - def regression(self): - change = self.change - adjusted_change = change if self.less_is_better else -change - return (self.confidence and adjusted_change > self.threshold) - - @property - def formatted(self): - fmt = formatter_for_unit(self.unit) - return { - "benchmark": self.name, - "change": change_fmt(self.change), - "regression": self.regression, - "baseline": fmt(self.baseline.value), - "contender": fmt(self.contender.value), - "unit": self.unit, - "less_is_better": self.less_is_better, - "counters": str(self.baseline.counters) - } - - def compare(self, comparator=None): - return { - "benchmark": self.name, - "change": self.change, - "regression": self.regression, - "baseline": self.baseline.value, - "contender": self.contender.value, - "unit": self.unit, - "less_is_better": self.less_is_better, - "counters": self.baseline.counters - } - - def __call__(self, **kwargs): - return self.compare(**kwargs) - - -def pairwise_compare(contender, baseline): - dict_contender = {e.name: e for e in contender} - dict_baseline = {e.name: e for e in baseline} - - for name in (dict_contender.keys() & dict_baseline.keys()): - yield name, (dict_contender[name], dict_baseline[name]) - - -class RunnerComparator: - """ Compares suites/benchmarks from runners. - - It is up to the caller that ensure that runners are compatible (both from - the same language implementation). - """ - - def __init__(self, contender, baseline, threshold=DEFAULT_THRESHOLD): - self.contender = contender - self.baseline = baseline - self.threshold = threshold - - @property - def comparisons(self): - contender = self.contender.suites - baseline = self.baseline.suites - suites = pairwise_compare(contender, baseline) - - for suite_name, (suite_cont, suite_base) in suites: - benchmarks = pairwise_compare( - suite_cont.benchmarks, suite_base.benchmarks) - - for _, (bench_cont, bench_base) in benchmarks: - yield BenchmarkComparator(bench_cont, bench_base, - threshold=self.threshold, - suite_name=suite_name) diff --git a/dev/archery/archery/benchmark/core.py b/dev/archery/archery/benchmark/core.py deleted file mode 100644 index 5a92271a3539..000000000000 --- a/dev/archery/archery/benchmark/core.py +++ /dev/null @@ -1,57 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - - -def median(values): - n = len(values) - if n == 0: - raise ValueError("median requires at least one value") - elif n % 2 == 0: - return (values[(n // 2) - 1] + values[n // 2]) / 2 - else: - return values[n // 2] - - -class Benchmark: - def __init__(self, name, unit, less_is_better, values, time_unit, - times, counters=None): - self.name = name - self.unit = unit - self.less_is_better = less_is_better - self.values = sorted(values) - self.time_unit = time_unit - self.times = sorted(times) - self.median = median(self.values) - self.counters = counters or {} - - @property - def value(self): - return self.median - - def __repr__(self): - return "Benchmark[name={},value={}]".format(self.name, self.value) - - -class BenchmarkSuite: - def __init__(self, name, benchmarks): - self.name = name - self.benchmarks = benchmarks - - def __repr__(self): - return "BenchmarkSuite[name={}, benchmarks={}]".format( - self.name, self.benchmarks - ) diff --git a/dev/archery/archery/benchmark/google.py b/dev/archery/archery/benchmark/google.py deleted file mode 100644 index ebcc5263645f..000000000000 --- a/dev/archery/archery/benchmark/google.py +++ /dev/null @@ -1,174 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -from itertools import filterfalse, groupby, tee -import json -import subprocess -from tempfile import NamedTemporaryFile - -from .core import Benchmark -from ..utils.command import Command - - -def partition(pred, iterable): - # adapted from python's examples - t1, t2 = tee(iterable) - return list(filter(pred, t1)), list(filterfalse(pred, t2)) - - -class GoogleBenchmarkCommand(Command): - """ Run a google benchmark binary. - - This assumes the binary supports the standard command line options, - notably `--benchmark_filter`, `--benchmark_format`, etc... - """ - - def __init__(self, benchmark_bin, benchmark_filter=None): - self.bin = benchmark_bin - self.benchmark_filter = benchmark_filter - - def list_benchmarks(self): - argv = ["--benchmark_list_tests"] - if self.benchmark_filter: - argv.append("--benchmark_filter={}".format(self.benchmark_filter)) - result = self.run(*argv, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - return str.splitlines(result.stdout.decode("utf-8")) - - def results(self, repetitions=1): - with NamedTemporaryFile() as out: - argv = ["--benchmark_repetitions={}".format(repetitions), - "--benchmark_out={}".format(out.name), - "--benchmark_out_format=json"] - - if self.benchmark_filter: - argv.append( - "--benchmark_filter={}".format(self.benchmark_filter) - ) - - self.run(*argv, check=True) - return json.load(out) - - -class GoogleBenchmarkObservation: - """ Represents one run of a single (google c++) benchmark. - - Aggregates are reported by Google Benchmark executables alongside - other observations whenever repetitions are specified (with - `--benchmark_repetitions` on the bare benchmark, or with the - archery option `--repetitions`). Aggregate observations are not - included in `GoogleBenchmark.runs`. - - RegressionSumKernel/32768/0 1 us 1 us 25.8077GB/s - RegressionSumKernel/32768/0 1 us 1 us 25.7066GB/s - RegressionSumKernel/32768/0 1 us 1 us 25.1481GB/s - RegressionSumKernel/32768/0 1 us 1 us 25.846GB/s - RegressionSumKernel/32768/0 1 us 1 us 25.6453GB/s - RegressionSumKernel/32768/0_mean 1 us 1 us 25.6307GB/s - RegressionSumKernel/32768/0_median 1 us 1 us 25.7066GB/s - RegressionSumKernel/32768/0_stddev 0 us 0 us 288.046MB/s - """ - - def __init__(self, name, real_time, cpu_time, time_unit, run_type, - size=None, bytes_per_second=None, items_per_second=None, - **counters): - self._name = name - self.real_time = real_time - self.cpu_time = cpu_time - self.time_unit = time_unit - self.run_type = run_type - self.size = size - self.bytes_per_second = bytes_per_second - self.items_per_second = items_per_second - self.counters = counters - - @property - def is_aggregate(self): - """ Indicate if the observation is a run or an aggregate. """ - return self.run_type == "aggregate" - - @property - def is_realtime(self): - """ Indicate if the preferred value is realtime instead of cputime. """ - return self.name.find("/real_time") != -1 - - @property - def name(self): - name = self._name - return name.rsplit("_", maxsplit=1)[0] if self.is_aggregate else name - - @property - def time(self): - return self.real_time if self.is_realtime else self.cpu_time - - @property - def value(self): - """ Return the benchmark value.""" - return self.bytes_per_second or self.items_per_second or self.time - - @property - def unit(self): - if self.bytes_per_second: - return "bytes_per_second" - elif self.items_per_second: - return "items_per_second" - else: - return self.time_unit - - def __repr__(self): - return str(self.value) - - -class GoogleBenchmark(Benchmark): - """ A set of GoogleBenchmarkObservations. """ - - def __init__(self, name, runs): - """ Initialize a GoogleBenchmark. - - Parameters - ---------- - name: str - Name of the benchmark - runs: list(GoogleBenchmarkObservation) - Repetitions of GoogleBenchmarkObservation run. - - """ - self.name = name - # exclude google benchmark aggregate artifacts - _, runs = partition(lambda b: b.is_aggregate, runs) - self.runs = sorted(runs, key=lambda b: b.value) - unit = self.runs[0].unit - time_unit = self.runs[0].time_unit - less_is_better = not unit.endswith("per_second") - values = [b.value for b in self.runs] - times = [b.real_time for b in self.runs] - # Slight kludge to extract the UserCounters for each benchmark - counters = self.runs[0].counters - super().__init__(name, unit, less_is_better, values, time_unit, times, - counters) - - def __repr__(self): - return "GoogleBenchmark[name={},runs={}]".format(self.names, self.runs) - - @classmethod - def from_json(cls, payload): - def group_key(x): - return x.name - - benchmarks = map(lambda x: GoogleBenchmarkObservation(**x), payload) - groups = groupby(sorted(benchmarks, key=group_key), group_key) - return [cls(k, list(bs)) for k, bs in groups] diff --git a/dev/archery/archery/benchmark/runner.py b/dev/archery/archery/benchmark/runner.py deleted file mode 100644 index 5718bcaf108c..000000000000 --- a/dev/archery/archery/benchmark/runner.py +++ /dev/null @@ -1,212 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -import glob -import json -import os -import re - -from .core import BenchmarkSuite -from .google import GoogleBenchmarkCommand, GoogleBenchmark -from ..lang.cpp import CppCMakeDefinition, CppConfiguration -from ..utils.cmake import CMakeBuild -from ..utils.logger import logger - - -def regex_filter(re_expr): - if re_expr is None: - return lambda s: True - re_comp = re.compile(re_expr) - return lambda s: re_comp.search(s) - - -DEFAULT_REPETITIONS = 1 - - -class BenchmarkRunner: - def __init__(self, suite_filter=None, benchmark_filter=None, - repetitions=DEFAULT_REPETITIONS): - self.suite_filter = suite_filter - self.benchmark_filter = benchmark_filter - self.repetitions = repetitions - - @property - def suites(self): - raise NotImplementedError("BenchmarkRunner must implement suites") - - @staticmethod - def from_rev_or_path(src, root, rev_or_path, cmake_conf, **kwargs): - """ Returns a BenchmarkRunner from a path or a git revision. - - First, it checks if `rev_or_path` is a valid path (or string) of a json - object that can deserialize to a BenchmarkRunner. If so, it initialize - a StaticBenchmarkRunner from it. This allows memoizing the result of a - run in a file or a string. - - Second, it checks if `rev_or_path` points to a valid CMake build - directory. If so, it creates a CppBenchmarkRunner with this existing - CMakeBuild. - - Otherwise, it assumes `rev_or_path` is a revision and clone/checkout - the given revision and create a fresh CMakeBuild. - """ - build = None - if StaticBenchmarkRunner.is_json_result(rev_or_path): - return StaticBenchmarkRunner.from_json(rev_or_path, **kwargs) - elif CMakeBuild.is_build_dir(rev_or_path): - build = CMakeBuild.from_path(rev_or_path) - return CppBenchmarkRunner(build, **kwargs) - else: - # Revisions can references remote via the `/` character, ensure - # that the revision is path friendly - path_rev = rev_or_path.replace("/", "_") - root_rev = os.path.join(root, path_rev) - os.mkdir(root_rev) - - clone_dir = os.path.join(root_rev, "arrow") - # Possibly checkout the sources at given revision, no need to - # perform cleanup on cloned repository as root_rev is reclaimed. - src_rev, _ = src.at_revision(rev_or_path, clone_dir) - cmake_def = CppCMakeDefinition(src_rev.cpp, cmake_conf) - build_dir = os.path.join(root_rev, "build") - return CppBenchmarkRunner(cmake_def.build(build_dir), **kwargs) - - -class StaticBenchmarkRunner(BenchmarkRunner): - """ Run suites from a (static) set of suites. """ - - def __init__(self, suites, **kwargs): - self._suites = suites - super().__init__(**kwargs) - - @property - def list_benchmarks(self): - for suite in self._suites: - for benchmark in suite.benchmarks: - yield "{}.{}".format(suite.name, benchmark.name) - - @property - def suites(self): - suite_fn = regex_filter(self.suite_filter) - benchmark_fn = regex_filter(self.benchmark_filter) - - for suite in (s for s in self._suites if suite_fn(s.name)): - benchmarks = [b for b in suite.benchmarks if benchmark_fn(b.name)] - yield BenchmarkSuite(suite.name, benchmarks) - - @classmethod - def is_json_result(cls, path_or_str): - builder = None - try: - builder = cls.from_json(path_or_str) - except BaseException: - pass - - return builder is not None - - @staticmethod - def from_json(path_or_str, **kwargs): - # .codec imported here to break recursive imports - from .codec import BenchmarkRunnerCodec - if os.path.isfile(path_or_str): - with open(path_or_str) as f: - loaded = json.load(f) - else: - loaded = json.loads(path_or_str) - return BenchmarkRunnerCodec.decode(loaded, **kwargs) - - def __repr__(self): - return "BenchmarkRunner[suites={}]".format(list(self.suites)) - - -class CppBenchmarkRunner(BenchmarkRunner): - """ Run suites from a CMakeBuild. """ - - def __init__(self, build, **kwargs): - """ Initialize a CppBenchmarkRunner. """ - self.build = build - super().__init__(**kwargs) - - @staticmethod - def default_configuration(**kwargs): - """ Returns the default benchmark configuration. """ - return CppConfiguration( - build_type="release", with_tests=False, with_benchmarks=True, - with_compute=True, - with_csv=True, - with_dataset=True, - with_json=True, - with_parquet=True, - with_python=False, - with_brotli=True, - with_bz2=True, - with_lz4=True, - with_snappy=True, - with_zlib=True, - with_zstd=True, - **kwargs) - - @property - def suites_binaries(self): - """ Returns a list of benchmark binaries for this build. """ - # Ensure build is up-to-date to run benchmarks - self.build() - # Not the best method, but works for now - glob_expr = os.path.join(self.build.binaries_dir, "*-benchmark") - return {os.path.basename(b): b for b in glob.glob(glob_expr)} - - def suite(self, name, suite_bin): - """ Returns the resulting benchmarks for a given suite. """ - suite_cmd = GoogleBenchmarkCommand(suite_bin, self.benchmark_filter) - - # Ensure there will be data - benchmark_names = suite_cmd.list_benchmarks() - if not benchmark_names: - return None - - results = suite_cmd.results(repetitions=self.repetitions) - benchmarks = GoogleBenchmark.from_json(results.get("benchmarks")) - return BenchmarkSuite(name, benchmarks) - - @property - def list_benchmarks(self): - for suite_name, suite_bin in self.suites_binaries.items(): - suite_cmd = GoogleBenchmarkCommand(suite_bin) - for benchmark_name in suite_cmd.list_benchmarks(): - yield "{}.{}".format(suite_name, benchmark_name) - - @property - def suites(self): - """ Returns all suite for a runner. """ - suite_matcher = regex_filter(self.suite_filter) - - suite_and_binaries = self.suites_binaries - for suite_name in suite_and_binaries: - if not suite_matcher(suite_name): - logger.debug("Ignoring suite {}".format(suite_name)) - continue - - suite_bin = suite_and_binaries[suite_name] - suite = self.suite(suite_name, suite_bin) - - # Filter may exclude all benchmarks - if not suite: - logger.debug("Suite {} executed but no results" - .format(suite_name)) - continue - - yield suite diff --git a/dev/archery/archery/bot.py b/dev/archery/archery/bot.py deleted file mode 100644 index c69cf9112da8..000000000000 --- a/dev/archery/archery/bot.py +++ /dev/null @@ -1,261 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -import os -import shlex -from pathlib import Path -from functools import partial -import tempfile - -import click -import github - -from .utils.git import git -from .utils.logger import logger -from .crossbow import Repo, Queue, Config, Target, Job, CommentReport - - -class EventError(Exception): - pass - - -class CommandError(Exception): - - def __init__(self, message): - self.message = message - - -class _CommandMixin: - - def get_help_option(self, ctx): - def show_help(ctx, param, value): - if value and not ctx.resilient_parsing: - raise click.UsageError(ctx.get_help()) - option = super().get_help_option(ctx) - option.callback = show_help - return option - - def __call__(self, message, **kwargs): - args = shlex.split(message) - try: - with self.make_context(self.name, args=args, obj=kwargs) as ctx: - return self.invoke(ctx) - except click.ClickException as e: - raise CommandError(e.format_message()) - - -class Command(_CommandMixin, click.Command): - pass - - -class Group(_CommandMixin, click.Group): - - def command(self, *args, **kwargs): - kwargs.setdefault('cls', Command) - return super().command(*args, **kwargs) - - def group(self, *args, **kwargs): - kwargs.setdefault('cls', Group) - return super().group(*args, **kwargs) - - def parse_args(self, ctx, args): - if not args and self.no_args_is_help and not ctx.resilient_parsing: - raise click.UsageError(ctx.get_help()) - return super().parse_args(ctx, args) - - -command = partial(click.command, cls=Command) -group = partial(click.group, cls=Group) - - -class CommentBot: - - def __init__(self, name, handler, token=None): - # TODO(kszucs): validate - assert isinstance(name, str) - assert callable(handler) - self.name = name - self.handler = handler - self.github = github.Github(token) - - def parse_command(self, payload): - # only allow users of apache org to submit commands, for more see - # https://developer.github.com/v4/enum/commentauthorassociation/ - allowed_roles = {'OWNER', 'MEMBER', 'CONTRIBUTOR'} - mention = '@{}'.format(self.name) - comment = payload['comment'] - - if payload['sender']['login'] == self.name: - raise EventError("Don't respond to itself") - elif payload['action'] not in {'created', 'edited'}: - raise EventError("Don't respond to comment deletion") - elif comment['author_association'] not in allowed_roles: - raise EventError( - "Don't respond to comments from non-authorized users" - ) - elif not comment['body'].lstrip().startswith(mention): - raise EventError("The bot is not mentioned") - - return payload['comment']['body'].split(mention)[-1].strip() - - def handle(self, event, payload): - try: - command = self.parse_command(payload) - except EventError as e: - logger.error(e) - # see the possible reasons in the validate method - return - - if event == 'issue_comment': - return self.handle_issue_comment(command, payload) - elif event == 'pull_request_review_comment': - return self.handle_review_comment(command, payload) - else: - raise ValueError("Unexpected event type {}".format(event)) - - def handle_issue_comment(self, command, payload): - repo = self.github.get_repo(payload['repository']['id'], lazy=True) - issue = repo.get_issue(payload['issue']['number']) - - try: - pull = issue.as_pull_request() - except github.GithubException: - return issue.create_comment( - "The comment bot only listens to pull request comments!" - ) - - comment = pull.get_issue_comment(payload['comment']['id']) - try: - self.handler(command, issue=issue, pull_request=pull, - comment=comment) - except CommandError as e: - logger.error(e) - pull.create_issue_comment("```\n{}\n```".format(e.message)) - except Exception as e: - logger.exception(e) - comment.create_reaction('-1') - else: - comment.create_reaction('+1') - - def handle_review_comment(self, payload): - raise NotImplementedError() - - -@group(name='@github-actions') -@click.pass_context -def actions(ctx): - """Ursabot""" - ctx.ensure_object(dict) - - -@actions.group() -@click.option('--crossbow', '-c', default='ursacomputing/crossbow', - help='Crossbow repository on github to use') -@click.pass_obj -def crossbow(obj, crossbow): - """ - Trigger crossbow builds for this pull request - """ - obj['crossbow_repo'] = crossbow - - -def _clone_arrow_and_crossbow(dest, crossbow_repo, pull_request): - """ - Clone the repositories and initialize crossbow objects. - - Parameters - ---------- - dest : Path - Filesystem path to clone the repositories to. - crossbow_repo : str - Github repository name, like kszucs/crossbow. - pull_request : pygithub.PullRequest - Object containing information about the pull request the comment bot - was triggered from. - """ - arrow_path = dest / 'arrow' - queue_path = dest / 'crossbow' - - # clone arrow and checkout the pull request's branch - pull_request_ref = 'pull/{}/head:{}'.format( - pull_request.number, pull_request.head.ref - ) - git.clone(pull_request.base.repo.clone_url, str(arrow_path)) - git.fetch('origin', pull_request_ref, git_dir=arrow_path) - git.checkout(pull_request.head.ref, git_dir=arrow_path) - - # clone crossbow repository - crossbow_url = 'https://github.com/{}'.format(crossbow_repo) - git.clone(crossbow_url, str(queue_path)) - - # initialize crossbow objects - github_token = os.environ['CROSSBOW_GITHUB_TOKEN'] - arrow = Repo(arrow_path) - queue = Queue(queue_path, github_token=github_token, require_https=True) - - return (arrow, queue) - - -@crossbow.command() -@click.argument('tasks', nargs=-1, required=False) -@click.option('--group', '-g', 'groups', multiple=True, - help='Submit task groups as defined in tests.yml') -@click.option('--param', '-p', 'params', multiple=True, - help='Additional task parameters for rendering the CI templates') -@click.option('--arrow-version', '-v', default=None, - help='Set target version explicitly.') -@click.pass_obj -def submit(obj, tasks, groups, params, arrow_version): - """ - Submit crossbow testing tasks. - - See groups defined in arrow/dev/tasks/tests.yml - """ - crossbow_repo = obj['crossbow_repo'] - pull_request = obj['pull_request'] - with tempfile.TemporaryDirectory() as tmpdir: - tmpdir = Path(tmpdir) - arrow, queue = _clone_arrow_and_crossbow( - dest=Path(tmpdir), - crossbow_repo=crossbow_repo, - pull_request=pull_request, - ) - # load available tasks configuration and groups from yaml - config = Config.load_yaml(arrow.path / "dev" / "tasks" / "tasks.yml") - config.validate() - - # initialize the crossbow build's target repository - target = Target.from_repo(arrow, version=arrow_version, - remote=pull_request.head.repo.clone_url, - branch=pull_request.head.ref) - - # parse additional job parameters - params = dict([p.split("=") for p in params]) - - # instantiate the job object - job = Job.from_config(config=config, target=target, tasks=tasks, - groups=groups, params=params) - - # add the job to the crossbow queue and push to the remote repository - queue.put(job, prefix="actions") - queue.push() - - # render the response comment's content - report = CommentReport(job, crossbow_repo=crossbow_repo) - - # send the response - pull_request.create_issue_comment(report.show()) diff --git a/dev/archery/archery/cli.py b/dev/archery/archery/cli.py deleted file mode 100644 index 4bbde75b74cf..000000000000 --- a/dev/archery/archery/cli.py +++ /dev/null @@ -1,1092 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -from collections import namedtuple -from io import StringIO -import click -import errno -import json -import logging -import os -import pathlib -import sys - -from .benchmark.codec import JsonEncoder -from .benchmark.compare import RunnerComparator, DEFAULT_THRESHOLD -from .benchmark.runner import BenchmarkRunner, CppBenchmarkRunner -from .lang.cpp import CppCMakeDefinition, CppConfiguration -from .utils.lint import linter, python_numpydoc, LintValidationException -from .utils.logger import logger, ctx as log_ctx -from .utils.source import ArrowSources, InvalidArrowSource -from .utils.tmpdir import tmpdir - -# Set default logging to INFO in command line. -logging.basicConfig(level=logging.INFO) - - -class ArrowBool(click.types.BoolParamType): - """ - ArrowBool supports the 'ON' and 'OFF' values on top of the values - supported by BoolParamType. This is convenient to port script which exports - CMake options variables. - """ - name = "boolean" - - def convert(self, value, param, ctx): - if isinstance(value, str): - lowered = value.lower() - if lowered == "on": - return True - elif lowered == "off": - return False - - return super().convert(value, param, ctx) - - -BOOL = ArrowBool() - - -@click.group() -@click.option("--debug", type=BOOL, is_flag=True, default=False, - help="Increase logging with debugging output.") -@click.option("--pdb", type=BOOL, is_flag=True, default=False, - help="Invoke pdb on uncaught exception.") -@click.option("-q", "--quiet", type=BOOL, is_flag=True, default=False, - help="Silence executed commands.") -@click.pass_context -def archery(ctx, debug, pdb, quiet): - """ Apache Arrow developer utilities. - - See sub-commands help with `archery --help`. - - """ - # Ensure ctx.obj exists - ctx.ensure_object(dict) - - log_ctx.quiet = quiet - if debug: - logger.setLevel(logging.DEBUG) - - ctx.debug = debug - - if pdb: - import pdb - sys.excepthook = lambda t, v, e: pdb.pm() - - -def validate_arrow_sources(ctx, param, src): - """ Ensure a directory contains Arrow cpp sources. """ - try: - return ArrowSources.find(src) - except InvalidArrowSource as e: - raise click.BadParameter(str(e)) - - -build_dir_type = click.Path(dir_okay=True, file_okay=False, resolve_path=True) -# Supported build types -build_type = click.Choice(["debug", "relwithdebinfo", "release"], - case_sensitive=False) -# Supported warn levels -warn_level_type = click.Choice(["everything", "checkin", "production"], - case_sensitive=False) - -simd_level = click.Choice(["NONE", "SSE4_2", "AVX2", "AVX512"], - case_sensitive=True) - - -def cpp_toolchain_options(cmd): - options = [ - click.option("--cc", metavar="", help="C compiler."), - click.option("--cxx", metavar="", help="C++ compiler."), - click.option("--cxx-flags", help="C++ compiler flags."), - click.option("--cpp-package-prefix", - help=("Value to pass for ARROW_PACKAGE_PREFIX and " - "use ARROW_DEPENDENCY_SOURCE=SYSTEM")) - ] - return _apply_options(cmd, options) - - -def _apply_options(cmd, options): - for option in options: - cmd = option(cmd) - return cmd - - -@archery.command(short_help="Initialize an Arrow C++ build") -@click.option("--src", metavar="", default=None, - callback=validate_arrow_sources, - help="Specify Arrow source directory") -# toolchain -@cpp_toolchain_options -@click.option("--build-type", default=None, type=build_type, - help="CMake's CMAKE_BUILD_TYPE") -@click.option("--warn-level", default="production", type=warn_level_type, - help="Controls compiler warnings -W(no-)error.") -@click.option("--use-gold-linker", default=True, type=BOOL, - help="Toggles ARROW_USE_LD_GOLD option.") -@click.option("--simd-level", default="SSE4_2", type=simd_level, - help="Toggles ARROW_SIMD_LEVEL option.") -# Tests and benchmarks -@click.option("--with-tests", default=True, type=BOOL, - help="Build with tests.") -@click.option("--with-benchmarks", default=None, type=BOOL, - help="Build with benchmarks.") -@click.option("--with-examples", default=None, type=BOOL, - help="Build with examples.") -@click.option("--with-integration", default=None, type=BOOL, - help="Build with integration test executables.") -# Static checks -@click.option("--use-asan", default=None, type=BOOL, - help="Toggle ARROW_USE_ASAN sanitizer.") -@click.option("--use-tsan", default=None, type=BOOL, - help="Toggle ARROW_USE_TSAN sanitizer.") -@click.option("--use-ubsan", default=None, type=BOOL, - help="Toggle ARROW_USE_UBSAN sanitizer.") -@click.option("--with-fuzzing", default=None, type=BOOL, - help="Toggle ARROW_FUZZING.") -# Components -@click.option("--with-compute", default=None, type=BOOL, - help="Build the Arrow compute module.") -@click.option("--with-csv", default=None, type=BOOL, - help="Build the Arrow CSV parser module.") -@click.option("--with-cuda", default=None, type=BOOL, - help="Build the Arrow CUDA extensions.") -@click.option("--with-dataset", default=None, type=BOOL, - help="Build the Arrow dataset module.") -@click.option("--with-filesystem", default=None, type=BOOL, - help="Build the Arrow filesystem layer.") -@click.option("--with-flight", default=None, type=BOOL, - help="Build with Flight rpc support.") -@click.option("--with-gandiva", default=None, type=BOOL, - help="Build with Gandiva expression compiler support.") -@click.option("--with-hdfs", default=None, type=BOOL, - help="Build the Arrow HDFS bridge.") -@click.option("--with-hiveserver2", default=None, type=BOOL, - help="Build the HiveServer2 client and arrow adapater.") -@click.option("--with-ipc", default=None, type=BOOL, - help="Build the Arrow IPC extensions.") -@click.option("--with-json", default=None, type=BOOL, - help="Build the Arrow JSON parser module.") -@click.option("--with-jni", default=None, type=BOOL, - help="Build the Arrow JNI lib.") -@click.option("--with-mimalloc", default=None, type=BOOL, - help="Build the Arrow mimalloc based allocator.") -@click.option("--with-parquet", default=None, type=BOOL, - help="Build with Parquet file support.") -@click.option("--with-plasma", default=None, type=BOOL, - help="Build with Plasma object store support.") -@click.option("--with-python", default=None, type=BOOL, - help="Build the Arrow CPython extesions.") -@click.option("--with-r", default=None, type=BOOL, - help="Build the Arrow R extensions. This is not a CMake option, " - "it will toggle required options") -@click.option("--with-s3", default=None, type=BOOL, - help="Build Arrow with S3 support.") -# Compressions -@click.option("--with-brotli", default=None, type=BOOL, - help="Build Arrow with brotli compression.") -@click.option("--with-bz2", default=None, type=BOOL, - help="Build Arrow with bz2 compression.") -@click.option("--with-lz4", default=None, type=BOOL, - help="Build Arrow with lz4 compression.") -@click.option("--with-snappy", default=None, type=BOOL, - help="Build Arrow with snappy compression.") -@click.option("--with-zlib", default=None, type=BOOL, - help="Build Arrow with zlib compression.") -@click.option("--with-zstd", default=None, type=BOOL, - help="Build Arrow with zstd compression.") -# CMake extra feature -@click.option("--cmake-extras", type=str, multiple=True, - help="Extra flags/options to pass to cmake invocation. " - "Can be stacked") -@click.option("--install-prefix", type=str, - help="Destination directory where files are installed. Expand to" - "CMAKE_INSTALL_PREFIX. Defaults to to $CONDA_PREFIX if the" - "variable exists.") -# misc -@click.option("-f", "--force", type=BOOL, is_flag=True, default=False, - help="Delete existing build directory if found.") -@click.option("--targets", type=str, multiple=True, - help="Generator targets to run. Can be stacked.") -@click.argument("build_dir", type=build_dir_type) -@click.pass_context -def build(ctx, src, build_dir, force, targets, **kwargs): - """ Initialize a C++ build directory. - - The build command creates a directory initialized with Arrow's cpp source - cmake and configuration. It can also optionally invoke the generator to - test the build (and used in scripts). - - Note that archery will carry the caller environment. It will also not touch - an existing directory, one must use the `--force` option to remove the - existing directory. - - Examples: - - \b - # Initialize build with clang8 and avx2 support in directory `clang8-build` - \b - archery build --cc=clang-8 --cxx=clang++-8 --cxx-flags=-mavx2 clang8-build - - \b - # Builds and run test - archery build --targets=all --targets=test build - """ - # Arrow's cpp cmake configuration - conf = CppConfiguration(**kwargs) - # This is a closure around cmake invocation, e.g. calling `def.build()` - # yields a directory ready to be run with the generator - cmake_def = CppCMakeDefinition(src.cpp, conf) - # Create build directory - build = cmake_def.build(build_dir, force=force) - - for target in targets: - build.run(target) - - -LintCheck = namedtuple('LintCheck', ('option_name', 'help')) - -lint_checks = [ - LintCheck('clang-format', "Format C++ files with clang-format."), - LintCheck('clang-tidy', "Lint C++ files with clang-tidy."), - LintCheck('cpplint', "Lint C++ files with cpplint."), - LintCheck('iwyu', "Lint changed C++ files with Include-What-You-Use."), - LintCheck('python', - "Format and lint Python files with autopep8 and flake8."), - LintCheck('numpydoc', "Lint Python files with numpydoc."), - LintCheck('cmake-format', "Format CMake files with cmake-format.py."), - LintCheck('rat', - "Check all sources files for license texts via Apache RAT."), - LintCheck('r', "Lint R files."), - LintCheck('rust', "Lint Rust files."), - LintCheck('docker', "Lint Dockerfiles with hadolint."), -] - - -def decorate_lint_command(cmd): - """ - Decorate the lint() command function to add individual per-check options. - """ - for check in lint_checks: - option = click.option("--{0}/--no-{0}".format(check.option_name), - default=None, help=check.help) - cmd = option(cmd) - return cmd - - -@archery.command(short_help="Check Arrow source tree for errors") -@click.option("--src", metavar="", default=".", - help="Specify Arrow source directory") -@click.option("--fix", is_flag=True, type=BOOL, default=False, - help="Toggle fixing the lint errors if the linter supports it.") -@click.option("--iwyu_all", is_flag=True, type=BOOL, default=False, - help="Run IWYU on all C++ files if enabled") -@click.option("-a", "--all", is_flag=True, default=False, - help="Enable all checks.") -@decorate_lint_command -@click.pass_context -def lint(ctx, src, fix, iwyu_all, **checks): - src = ArrowSources(src) - - if checks.pop('all'): - # "--all" is given => enable all non-selected checks - for k, v in checks.items(): - if v is None: - checks[k] = True - if not any(checks.values()): - raise click.UsageError( - "Need to enable at least one lint check (try --help)") - try: - linter(src, fix, iwyu_all=iwyu_all, **checks) - except LintValidationException: - sys.exit(1) - - -@archery.command(short_help="Lint python docstring with NumpyDoc") -@click.argument('symbols', nargs=-1) -@click.option("--src", metavar="", default=None, - callback=validate_arrow_sources, - help="Specify Arrow source directory") -@click.option("--allow-rule", "-a", multiple=True, - help="Allow only these rules") -@click.option("--disallow-rule", "-d", multiple=True, - help="Disallow these rules") -def numpydoc(src, symbols, allow_rule, disallow_rule): - """ - Pass list of modules or symbols as arguments to restrict the validation. - - By default all modules of pyarrow are tried to be validated. - - Examples - -------- - archery numpydoc pyarrow.dataset - archery numpydoc pyarrow.csv pyarrow.json pyarrow.parquet - archery numpydoc pyarrow.array - """ - disallow_rule = disallow_rule or {'GL01', 'SA01', 'EX01', 'ES01'} - try: - results = python_numpydoc(symbols, allow_rules=allow_rule, - disallow_rule=disallow_rule) - for result in results: - result.ok() - except LintValidationException: - sys.exit(1) - - -@archery.group() -@click.pass_context -def benchmark(ctx): - """ Arrow benchmarking. - - Use the diff sub-command to benchmark revisions, and/or build directories. - """ - pass - - -def benchmark_common_options(cmd): - options = [ - click.option("--src", metavar="", show_default=True, - default=None, callback=validate_arrow_sources, - help="Specify Arrow source directory"), - click.option("--preserve", type=BOOL, default=False, show_default=True, - is_flag=True, - help="Preserve workspace for investigation."), - click.option("--output", metavar="", - type=click.File("w", encoding="utf8"), default="-", - help="Capture output result into file."), - click.option("--cmake-extras", type=str, multiple=True, - help="Extra flags/options to pass to cmake invocation. " - "Can be stacked"), - ] - - cmd = cpp_toolchain_options(cmd) - return _apply_options(cmd, options) - - -def benchmark_filter_options(cmd): - options = [ - click.option("--suite-filter", metavar="", show_default=True, - type=str, default=None, - help="Regex filtering benchmark suites."), - click.option("--benchmark-filter", metavar="", - show_default=True, type=str, default=None, - help="Regex filtering benchmarks.") - ] - return _apply_options(cmd, options) - - -@benchmark.command(name="list", short_help="List benchmark suite") -@click.argument("rev_or_path", metavar="[]", - default="WORKSPACE", required=False) -@benchmark_common_options -@click.pass_context -def benchmark_list(ctx, rev_or_path, src, preserve, output, cmake_extras, - **kwargs): - """ List benchmark suite. - """ - with tmpdir(preserve=preserve) as root: - logger.debug("Running benchmark {}".format(rev_or_path)) - - conf = CppBenchmarkRunner.default_configuration( - cmake_extras=cmake_extras, **kwargs) - - runner_base = BenchmarkRunner.from_rev_or_path( - src, root, rev_or_path, conf) - - for b in runner_base.list_benchmarks: - click.echo(b, file=output) - - -@benchmark.command(name="run", short_help="Run benchmark suite") -@click.argument("rev_or_path", metavar="[]", - default="WORKSPACE", required=False) -@benchmark_common_options -@benchmark_filter_options -@click.option("--repetitions", type=int, default=1, show_default=True, - help=("Number of repetitions of each benchmark. Increasing " - "may improve result precision.")) -@click.pass_context -def benchmark_run(ctx, rev_or_path, src, preserve, output, cmake_extras, - suite_filter, benchmark_filter, repetitions, **kwargs): - """ Run benchmark suite. - - This command will run the benchmark suite for a single build. This is - used to capture (and/or publish) the results. - - The caller can optionally specify a target which is either a git revision - (commit, tag, special values like HEAD) or a cmake build directory. - - When a commit is referenced, a local clone of the arrow sources (specified - via --src) is performed and the proper branch is created. This is done in - a temporary directory which can be left intact with the `--preserve` flag. - - The special token "WORKSPACE" is reserved to specify the current git - workspace. This imply that no clone will be performed. - - Examples: - - \b - # Run the benchmarks on current git workspace - \b - archery benchmark run - - \b - # Run the benchmarks on current previous commit - \b - archery benchmark run HEAD~1 - - \b - # Run the benchmarks on current previous commit - \b - archery benchmark run --output=run.json - """ - with tmpdir(preserve=preserve) as root: - logger.debug("Running benchmark {}".format(rev_or_path)) - - conf = CppBenchmarkRunner.default_configuration( - cmake_extras=cmake_extras, **kwargs) - - runner_base = BenchmarkRunner.from_rev_or_path( - src, root, rev_or_path, conf, - repetitions=repetitions, - suite_filter=suite_filter, benchmark_filter=benchmark_filter) - - json.dump(runner_base, output, cls=JsonEncoder) - - -@benchmark.command(name="diff", short_help="Compare benchmark suites") -@benchmark_common_options -@benchmark_filter_options -@click.option("--threshold", type=float, default=DEFAULT_THRESHOLD, - show_default=True, - help="Regression failure threshold in percentage.") -@click.option("--repetitions", type=int, default=1, show_default=True, - help=("Number of repetitions of each benchmark. Increasing " - "may improve result precision.")) -@click.option("--no-counters", type=BOOL, default=False, is_flag=True, - help="Hide counters field in diff report.") -@click.argument("contender", metavar="[", - default=ArrowSources.WORKSPACE, required=False) -@click.argument("baseline", metavar="[]]", default="origin/master", - required=False) -@click.pass_context -def benchmark_diff(ctx, src, preserve, output, cmake_extras, - suite_filter, benchmark_filter, repetitions, no_counters, - threshold, contender, baseline, **kwargs): - """Compare (diff) benchmark runs. - - This command acts like git-diff but for benchmark results. - - The caller can optionally specify both the contender and the baseline. If - unspecified, the contender will default to the current workspace (like git) - and the baseline will default to master. - - Each target (contender or baseline) can either be a git revision - (commit, tag, special values like HEAD) or a cmake build directory. This - allow comparing git commits, and/or different compilers and/or compiler - flags. - - When a commit is referenced, a local clone of the arrow sources (specified - via --src) is performed and the proper branch is created. This is done in - a temporary directory which can be left intact with the `--preserve` flag. - - The special token "WORKSPACE" is reserved to specify the current git - workspace. This imply that no clone will be performed. - - Examples: - - \b - # Compare workspace (contender) with master (baseline) - \b - archery benchmark diff - - \b - # Compare master (contender) with latest version (baseline) - \b - export LAST=$(git tag -l "apache-arrow-[0-9]*" | sort -rV | head -1) - \b - archery benchmark diff master "$LAST" - - \b - # Compare g++7 (contender) with clang++-8 (baseline) builds - \b - archery build --with-benchmarks=true \\ - --cxx-flags=-ftree-vectorize \\ - --cc=gcc-7 --cxx=g++-7 gcc7-build - \b - archery build --with-benchmarks=true \\ - --cxx-flags=-flax-vector-conversions \\ - --cc=clang-8 --cxx=clang++-8 clang8-build - \b - archery benchmark diff gcc7-build clang8-build - - \b - # Compare default targets but scoped to the suites matching - # `^arrow-compute-aggregate` and benchmarks matching `(Sum|Mean)Kernel`. - \b - archery benchmark diff --suite-filter="^arrow-compute-aggregate" \\ - --benchmark-filter="(Sum|Mean)Kernel" - - \b - # Capture result in file `result.json` - \b - archery benchmark diff --output=result.json - \b - # Equivalently with no stdout clutter. - archery --quiet benchmark diff > result.json - - \b - # Comparing with a cached results from `archery benchmark run` - \b - archery benchmark run --output=run.json HEAD~1 - \b - # This should not recompute the benchmark from run.json - archery --quiet benchmark diff WORKSPACE run.json > result.json - """ - with tmpdir(preserve=preserve) as root: - logger.debug("Comparing {} (contender) with {} (baseline)" - .format(contender, baseline)) - - conf = CppBenchmarkRunner.default_configuration( - cmake_extras=cmake_extras, **kwargs) - - runner_cont = BenchmarkRunner.from_rev_or_path( - src, root, contender, conf, - repetitions=repetitions, - suite_filter=suite_filter, - benchmark_filter=benchmark_filter) - runner_base = BenchmarkRunner.from_rev_or_path( - src, root, baseline, conf, - repetitions=repetitions, - suite_filter=suite_filter, - benchmark_filter=benchmark_filter) - - runner_comp = RunnerComparator(runner_cont, runner_base, threshold) - - # TODO(kszucs): test that the output is properly formatted jsonlines - comparisons_json = _get_comparisons_as_json(runner_comp.comparisons) - formatted = _format_comparisons_with_pandas(comparisons_json, - no_counters) - output.write(formatted) - output.write('\n') - - -def _get_comparisons_as_json(comparisons): - buf = StringIO() - for comparator in comparisons: - json.dump(comparator, buf, cls=JsonEncoder) - buf.write("\n") - - return buf.getvalue() - - -def _format_comparisons_with_pandas(comparisons_json, no_counters): - import pandas as pd - df = pd.read_json(StringIO(comparisons_json), lines=True) - # parse change % so we can sort by it - df['change %'] = df.pop('change').str[:-1].map(float) - first_regression = len(df) - df['regression'].sum() - - fields = ['benchmark', 'baseline', 'contender', 'change %'] - if not no_counters: - fields += ['counters'] - - df = df[fields].sort_values(by='change %', ascending=False) - - def labelled(title, df): - if len(df) == 0: - return '' - title += ': ({})'.format(len(df)) - df_str = df.to_string(index=False) - bar = '-' * df_str.index('\n') - return '\n'.join([bar, title, bar, df_str]) - - return '\n\n'.join([labelled('Non-regressions', df[:first_regression]), - labelled('Regressions', df[first_regression:])]) - - -# ---------------------------------------------------------------------- -# Integration testing - -def _set_default(opt, default): - if opt is None: - return default - return opt - - -@archery.command(short_help="Execute protocol and Flight integration tests") -@click.option('--with-all', is_flag=True, default=False, - help=('Include all known languages by default ' - 'in integration tests')) -@click.option('--random-seed', type=int, default=12345, - help="Seed for PRNG when generating test data") -@click.option('--with-cpp', type=bool, default=False, - help='Include C++ in integration tests') -@click.option('--with-java', type=bool, default=False, - help='Include Java in integration tests') -@click.option('--with-js', type=bool, default=False, - help='Include JavaScript in integration tests') -@click.option('--with-go', type=bool, default=False, - help='Include Go in integration tests') -@click.option('--with-rust', type=bool, default=False, - help='Include Rust in integration tests') -@click.option('--write_generated_json', default=False, - help='Generate test JSON to indicated path') -@click.option('--run-flight', is_flag=True, default=False, - help='Run Flight integration tests') -@click.option('--debug', is_flag=True, default=False, - help='Run executables in debug mode as relevant') -@click.option('--serial', is_flag=True, default=False, - help='Run tests serially, rather than in parallel') -@click.option('--tempdir', default=None, - help=('Directory to use for writing ' - 'integration test temporary files')) -@click.option('stop_on_error', '-x', '--stop-on-error', - is_flag=True, default=False, - help='Stop on first error') -@click.option('--gold-dirs', multiple=True, - help="gold integration test file paths") -@click.option('-k', '--match', - help=("Substring for test names to include in run, " - "e.g. -k primitive")) -def integration(with_all=False, random_seed=12345, **args): - from .integration.runner import write_js_test_json, run_all_tests - import numpy as np - - # FIXME(bkietz) Include help strings for individual testers. - # For example, CPPTester's ARROW_CPP_EXE_PATH environment variable. - - # Make runs involving data generation deterministic - np.random.seed(random_seed) - - gen_path = args['write_generated_json'] - - languages = ['cpp', 'java', 'js', 'go', 'rust'] - - enabled_languages = 0 - for lang in languages: - param = 'with_{}'.format(lang) - if with_all: - args[param] = with_all - - if args[param]: - enabled_languages += 1 - - if gen_path: - try: - os.makedirs(gen_path) - except OSError as e: - if e.errno != errno.EEXIST: - raise - write_js_test_json(gen_path) - else: - if enabled_languages == 0: - raise Exception("Must enable at least 1 language to test") - run_all_tests(**args) - - -@archery.command() -@click.option('--event-name', '-n', required=True) -@click.option('--event-payload', '-p', type=click.File('r', encoding='utf8'), - default='-', required=True) -@click.option('--arrow-token', envvar='ARROW_GITHUB_TOKEN', - help='OAuth token for responding comment in the arrow repo') -@click.option('--crossbow-token', '-ct', envvar='CROSSBOW_GITHUB_TOKEN', - help='OAuth token for pushing to the crossow repository') -def trigger_bot(event_name, event_payload, arrow_token, crossbow_token): - from .bot import CommentBot, actions - - event_payload = json.loads(event_payload.read()) - - bot = CommentBot(name='github-actions', handler=actions, token=arrow_token) - bot.handle(event_name, event_payload) - - -def _mock_compose_calls(compose): - from types import MethodType - from subprocess import CompletedProcess - - def _mock(compose, executable): - def _execute(self, *args, **kwargs): - params = ['{}={}'.format(k, v) - for k, v in self.config.params.items()] - command = ' '.join(params + [executable] + list(args)) - click.echo(command) - return CompletedProcess([], 0) - return MethodType(_execute, compose) - - compose._execute_docker = _mock(compose, executable='docker') - compose._execute_compose = _mock(compose, executable='docker-compose') - - -@archery.group('docker') -@click.option("--src", metavar="", default=None, - callback=validate_arrow_sources, - help="Specify Arrow source directory.") -@click.option('--dry-run/--execute', default=False, - help="Display the docker-compose commands instead of executing " - "them.") -@click.pass_obj -def docker_compose(obj, src, dry_run): - """Interact with docker-compose based builds.""" - from .docker import DockerCompose - - config_path = src.path / 'docker-compose.yml' - if not config_path.exists(): - raise click.ClickException( - "Docker compose configuration cannot be found in directory {}, " - "try to pass the arrow source directory explicitly.".format(src) - ) - - # take the docker-compose parameters like PYTHON, PANDAS, UBUNTU from the - # environment variables to keep the usage similar to docker-compose - compose = DockerCompose(config_path, params=os.environ) - if dry_run: - _mock_compose_calls(compose) - obj['compose'] = compose - - -@docker_compose.command('build') -@click.argument('image') -@click.option('--force-pull/--no-pull', default=True, - help="Whether to force pull the image and its ancestor images") -@click.option('--using-docker-cli', default=False, is_flag=True, - envvar='ARCHERY_USE_DOCKER_CLI', - help="Use docker CLI directly for building instead of calling " - "docker-compose. This may help to reuse cached layers.") -@click.option('--using-docker-buildx', default=False, is_flag=True, - envvar='ARCHERY_USE_DOCKER_BUILDX', - help="Use buildx with docker CLI directly for building instead " - "of calling docker-compose or the plain docker build " - "command. This option makes the build cache reusable " - "across hosts.") -@click.option('--use-cache/--no-cache', default=True, - help="Whether to use cache when building the image and its " - "ancestor images") -@click.option('--use-leaf-cache/--no-leaf-cache', default=True, - help="Whether to use cache when building only the (leaf) image " - "passed as the argument. To disable caching for both the " - "image and its ancestors use --no-cache option.") -@click.pass_obj -def docker_compose_build(obj, image, *, force_pull, using_docker_cli, - using_docker_buildx, use_cache, use_leaf_cache): - """ - Execute docker-compose builds. - """ - from .docker import UndefinedImage - - compose = obj['compose'] - - using_docker_cli |= using_docker_buildx - try: - if force_pull: - compose.pull(image, pull_leaf=use_leaf_cache, - using_docker=using_docker_cli) - compose.build(image, use_cache=use_cache, - use_leaf_cache=use_leaf_cache, - using_docker=using_docker_cli, - using_buildx=using_docker_buildx) - except UndefinedImage as e: - raise click.ClickException( - "There is no service/image defined in docker-compose.yml with " - "name: {}".format(str(e)) - ) - except RuntimeError as e: - raise click.ClickException(str(e)) - - -@docker_compose.command('run') -@click.argument('image') -@click.argument('command', required=False, default=None) -@click.option('--env', '-e', multiple=True, - help="Set environment variable within the container") -@click.option('--user', '-u', default=None, - help="Username or UID to run the container with") -@click.option('--force-pull/--no-pull', default=True, - help="Whether to force pull the image and its ancestor images") -@click.option('--force-build/--no-build', default=True, - help="Whether to force build the image and its ancestor images") -@click.option('--build-only', default=False, is_flag=True, - help="Pull and/or build the image, but do not run it") -@click.option('--using-docker-cli', default=False, is_flag=True, - envvar='ARCHERY_USE_DOCKER_CLI', - help="Use docker CLI directly for building instead of calling " - "docker-compose. This may help to reuse cached layers.") -@click.option('--using-docker-buildx', default=False, is_flag=True, - envvar='ARCHERY_USE_DOCKER_BUILDX', - help="Use buildx with docker CLI directly for building instead " - "of calling docker-compose or the plain docker build " - "command. This option makes the build cache reusable " - "across hosts.") -@click.option('--use-cache/--no-cache', default=True, - help="Whether to use cache when building the image and its " - "ancestor images") -@click.option('--use-leaf-cache/--no-leaf-cache', default=True, - help="Whether to use cache when building only the (leaf) image " - "passed as the argument. To disable caching for both the " - "image and its ancestors use --no-cache option.") -@click.option('--volume', '-v', multiple=True, - help="Set volume within the container") -@click.pass_obj -def docker_compose_run(obj, image, command, *, env, user, force_pull, - force_build, build_only, using_docker_cli, - using_docker_buildx, use_cache, - use_leaf_cache, volume): - """Execute docker-compose builds. - - To see the available builds run `archery docker images`. - - Examples: - - # execute a single build - archery docker run conda-python - - # execute the builds but disable the image pulling - archery docker run --no-cache conda-python - - # pass a docker-compose parameter, like the python version - PYTHON=3.8 archery docker run conda-python - - # disable the cache only for the leaf image - PANDAS=master archery docker run --no-leaf-cache conda-python-pandas - - # entirely skip building the image - archery docker run --no-pull --no-build conda-python - - # pass runtime parameters via docker environment variables - archery docker run -e CMAKE_BUILD_TYPE=release ubuntu-cpp - - # set a volume - archery docker run -v $PWD/build:/build ubuntu-cpp - - # starting an interactive bash session for debugging - archery docker run ubuntu-cpp bash - """ - from .docker import UndefinedImage - - compose = obj['compose'] - using_docker_cli |= using_docker_buildx - - env = dict(kv.split('=', 1) for kv in env) - try: - if force_pull: - compose.pull(image, pull_leaf=use_leaf_cache, - using_docker=using_docker_cli) - if force_build: - compose.build(image, use_cache=use_cache, - use_leaf_cache=use_leaf_cache, - using_docker=using_docker_cli, - using_buildx=using_docker_buildx) - if build_only: - return - compose.run( - image, - command=command, - env=env, - user=user, - using_docker=using_docker_cli, - volumes=volume - ) - except UndefinedImage as e: - raise click.ClickException( - "There is no service/image defined in docker-compose.yml with " - "name: {}".format(str(e)) - ) - except RuntimeError as e: - raise click.ClickException(str(e)) - - -@docker_compose.command('push') -@click.argument('image') -@click.option('--user', '-u', required=False, envvar='ARCHERY_DOCKER_USER', - help='Docker repository username') -@click.option('--password', '-p', required=False, - envvar='ARCHERY_DOCKER_PASSWORD', - help='Docker repository password') -@click.option('--using-docker-cli', default=False, is_flag=True, - help="Use docker CLI directly for building instead of calling " - "docker-compose. This may help to reuse cached layers.") -@click.pass_obj -def docker_compose_push(obj, image, user, password, using_docker_cli): - """Push the generated docker-compose image.""" - compose = obj['compose'] - compose.push(image, user=user, password=password, - using_docker=using_docker_cli) - - -@docker_compose.command('images') -@click.pass_obj -def docker_compose_images(obj): - """List the available docker-compose images.""" - compose = obj['compose'] - click.echo('Available images:') - for image in compose.images(): - click.echo(' - {}'.format(image)) - - -@archery.group('release') -@click.option("--src", metavar="", default=None, - callback=validate_arrow_sources, - help="Specify Arrow source directory.") -@click.option("--jira-cache", type=click.Path(), default=None, - help="File path to cache queried JIRA issues per version.") -@click.pass_obj -def release(obj, src, jira_cache): - """Release releated commands.""" - from .release import Jira, CachedJira - - jira = Jira() - if jira_cache is not None: - jira = CachedJira(jira_cache, jira=jira) - - obj['jira'] = jira - obj['repo'] = src.path - - -@release.command('curate') -@click.argument('version') -@click.pass_obj -def release_curate(obj, version): - """Release curation.""" - from .release import Release - - release = Release.from_jira(version, jira=obj['jira'], repo=obj['repo']) - curation = release.curate() - - click.echo(curation.render('console')) - - -@release.group('changelog') -def release_changelog(): - """Release changelog.""" - pass - - -@release_changelog.command('add') -@click.argument('version') -@click.pass_obj -def release_changelog_add(obj, version): - """Prepend the changelog with the current release""" - from .release import Release - - jira, repo = obj['jira'], obj['repo'] - - # just handle the current version - release = Release.from_jira(version, jira=jira, repo=repo) - if release.is_released: - raise ValueError('This version has been already released!') - - changelog = release.changelog() - changelog_path = pathlib.Path(repo) / 'CHANGELOG.md' - - current_content = changelog_path.read_text() - new_content = changelog.render('markdown') + current_content - - changelog_path.write_text(new_content) - click.echo("CHANGELOG.md is updated!") - - -@release_changelog.command('generate') -@click.argument('version') -@click.argument('output', type=click.File('w', encoding='utf8'), default='-') -@click.pass_obj -def release_changelog_generate(obj, version, output): - """Generate the changelog of a specific release.""" - from .release import Release - - jira, repo = obj['jira'], obj['repo'] - - # just handle the current version - release = Release.from_jira(version, jira=jira, repo=repo) - - changelog = release.changelog() - output.write(changelog.render('markdown')) - - -@release_changelog.command('regenerate') -@click.pass_obj -def release_changelog_regenerate(obj): - """Regeneretate the whole CHANGELOG.md file""" - from .release import Release - - jira, repo = obj['jira'], obj['repo'] - changelogs = [] - - for version in jira.arrow_versions(): - if not version.released: - continue - release = Release.from_jira(version, jira=jira, repo=repo) - click.echo('Querying changelog for version: {}'.format(version)) - changelogs.append(release.changelog()) - - click.echo('Rendering new CHANGELOG.md file...') - changelog_path = pathlib.Path(repo) / 'CHANGELOG.md' - with changelog_path.open('w') as fp: - for cl in changelogs: - fp.write(cl.render('markdown')) - - -@release.command('cherry-pick') -@click.argument('version') -@click.option('--dry-run/--execute', default=True, - help="Display the git commands instead of executing them.") -@click.option('--recreate/--continue', default=True, - help="Recreate the maintenance branch or only apply unapplied " - "patches.") -@click.pass_obj -def release_cherry_pick(obj, version, dry_run, recreate): - """ - Cherry pick commits. - """ - from .release import Release, MinorRelease, PatchRelease - - release = Release.from_jira(version, jira=obj['jira'], repo=obj['repo']) - if not isinstance(release, (MinorRelease, PatchRelease)): - raise click.UsageError('Cherry-pick command only supported for minor ' - 'and patch releases') - - if not dry_run: - release.cherry_pick_commits(recreate_branch=recreate) - click.echo('Executed the following commands:\n') - - click.echo( - 'git checkout {} -b {}'.format(release.previous.tag, release.branch) - ) - for commit in release.commits_to_pick(): - click.echo('git cherry-pick {}'.format(commit.hexsha)) - - -try: - from .crossbow.cli import crossbow # noqa -except ImportError as exc: - missing_package = exc.name - - @archery.command( - 'crossbow', - context_settings={"ignore_unknown_options": True} - ) - def crossbow(): - raise click.ClickException( - "Couldn't import crossbow because of missing dependency: {}" - .format(missing_package) - ) -else: - archery.add_command(crossbow) - - -if __name__ == "__main__": - archery(obj={}) diff --git a/dev/archery/archery/compat.py b/dev/archery/archery/compat.py deleted file mode 100644 index 22cb9fc7957d..000000000000 --- a/dev/archery/archery/compat.py +++ /dev/null @@ -1,51 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -import pathlib - - -def _is_path_like(path): - # PEP519 filesystem path protocol is available from python 3.6, so pathlib - # doesn't implement __fspath__ for earlier versions - return (isinstance(path, str) or - hasattr(path, '__fspath__') or - isinstance(path, pathlib.Path)) - - -def _ensure_path(path): - if isinstance(path, pathlib.Path): - return path - else: - return pathlib.Path(_stringify_path(path)) - - -def _stringify_path(path): - """ - Convert *path* to a string or unicode path if possible. - """ - if isinstance(path, str): - return path - - # checking whether path implements the filesystem protocol - try: - return path.__fspath__() # new in python 3.6 - except AttributeError: - # fallback pathlib ckeck for earlier python versions than 3.6 - if isinstance(path, pathlib.Path): - return str(path) - - raise TypeError("not a path-like object") diff --git a/dev/archery/archery/crossbow/__init__.py b/dev/archery/archery/crossbow/__init__.py deleted file mode 100644 index bc72e81f0505..000000000000 --- a/dev/archery/archery/crossbow/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -from .core import Config, Repo, Queue, Target, Job # noqa -from .reports import CommentReport, ConsoleReport, EmailReport # noqa diff --git a/dev/archery/archery/crossbow/cli.py b/dev/archery/archery/crossbow/cli.py deleted file mode 100644 index 71c25e0460f1..000000000000 --- a/dev/archery/archery/crossbow/cli.py +++ /dev/null @@ -1,352 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -from pathlib import Path - -import click - -from .core import Config, Repo, Queue, Target, Job, CrossbowError -from .reports import EmailReport, ConsoleReport -from ..utils.source import ArrowSources - - -_default_arrow_path = ArrowSources.find().path -_default_queue_path = _default_arrow_path.parent / "crossbow" -_default_config_path = _default_arrow_path / "dev" / "tasks" / "tasks.yml" - - -@click.group() -@click.option('--github-token', '-t', default=None, - envvar="CROSSBOW_GITHUB_TOKEN", - help='OAuth token for GitHub authentication') -@click.option('--arrow-path', '-a', - type=click.Path(), default=_default_arrow_path, - help='Arrow\'s repository path. Defaults to the repository of ' - 'this script') -@click.option('--queue-path', '-q', - type=click.Path(), default=_default_queue_path, - help='The repository path used for scheduling the tasks. ' - 'Defaults to crossbow directory placed next to arrow') -@click.option('--queue-remote', '-qr', default=None, - help='Force to use this remote URL for the Queue repository') -@click.option('--output-file', metavar='', - type=click.File('w', encoding='utf8'), default='-', - help='Capture output result into file.') -@click.pass_context -def crossbow(ctx, github_token, arrow_path, queue_path, queue_remote, - output_file): - """ - Schedule packaging tasks or nightly builds on CI services. - """ - ctx.ensure_object(dict) - ctx.obj['output'] = output_file - ctx.obj['arrow'] = Repo(arrow_path) - ctx.obj['queue'] = Queue(queue_path, remote_url=queue_remote, - github_token=github_token, require_https=True) - - -@crossbow.command() -@click.option('--config-path', '-c', - type=click.Path(exists=True), default=_default_config_path, - help='Task configuration yml. Defaults to tasks.yml') -@click.pass_obj -def check_config(obj, config_path): - # load available tasks configuration and groups from yaml - config = Config.load_yaml(config_path) - config.validate() - - output = obj['output'] - config.show(output) - - -@crossbow.command() -@click.argument('tasks', nargs=-1, required=False) -@click.option('--group', '-g', 'groups', multiple=True, - help='Submit task groups as defined in task.yml') -@click.option('--param', '-p', 'params', multiple=True, - help='Additional task parameters for rendering the CI templates') -@click.option('--job-prefix', default='build', - help='Arbitrary prefix for branch names, e.g. nightly') -@click.option('--config-path', '-c', - type=click.Path(exists=True), default=_default_config_path, - help='Task configuration yml. Defaults to tasks.yml') -@click.option('--arrow-version', '-v', default=None, - help='Set target version explicitly.') -@click.option('--arrow-remote', '-r', default=None, - help='Set GitHub remote explicitly, which is going to be cloned ' - 'on the CI services. Note, that no validation happens ' - 'locally. Examples: https://github.com/apache/arrow or ' - 'https://github.com/kszucs/arrow.') -@click.option('--arrow-branch', '-b', default=None, - help='Give the branch name explicitly, e.g. master, ARROW-1949.') -@click.option('--arrow-sha', '-t', default=None, - help='Set commit SHA or Tag name explicitly, e.g. f67a515, ' - 'apache-arrow-0.11.1.') -@click.option('--fetch/--no-fetch', default=True, - help='Fetch references (branches and tags) from the remote') -@click.option('--dry-run/--commit', default=False, - help='Just display the rendered CI configurations without ' - 'committing them') -@click.option('--no-push/--push', default=False, - help='Don\'t push the changes') -@click.pass_obj -def submit(obj, tasks, groups, params, job_prefix, config_path, arrow_version, - arrow_remote, arrow_branch, arrow_sha, fetch, dry_run, no_push): - output = obj['output'] - queue, arrow = obj['queue'], obj['arrow'] - - # load available tasks configuration and groups from yaml - config = Config.load_yaml(config_path) - try: - config.validate() - except CrossbowError as e: - raise click.ClickException(str(e)) - - # Override the detected repo url / remote, branch and sha - this aims to - # make release procedure a bit simpler. - # Note, that the target resivion's crossbow templates must be - # compatible with the locally checked out version of crossbow (which is - # in case of the release procedure), because the templates still - # contain some business logic (dependency installation, deployments) - # which will be reduced to a single command in the future. - target = Target.from_repo(arrow, remote=arrow_remote, branch=arrow_branch, - head=arrow_sha, version=arrow_version) - - # parse additional job parameters - params = dict([p.split("=") for p in params]) - - # instantiate the job object - try: - job = Job.from_config(config=config, target=target, tasks=tasks, - groups=groups, params=params) - except CrossbowError as e: - raise click.ClickException(str(e)) - - job.show(output) - if dry_run: - return - - if fetch: - queue.fetch() - queue.put(job, prefix=job_prefix) - - if no_push: - click.echo('Branches and commits created but not pushed: `{}`' - .format(job.branch)) - else: - queue.push() - click.echo('Pushed job identifier is: `{}`'.format(job.branch)) - - -@crossbow.command() -@click.argument('task', required=True) -@click.option('--config-path', '-c', - type=click.Path(exists=True), default=_default_config_path, - help='Task configuration yml. Defaults to tasks.yml') -@click.option('--arrow-version', '-v', default=None, - help='Set target version explicitly.') -@click.option('--arrow-remote', '-r', default=None, - help='Set GitHub remote explicitly, which is going to be cloned ' - 'on the CI services. Note, that no validation happens ' - 'locally. Examples: https://github.com/apache/arrow or ' - 'https://github.com/kszucs/arrow.') -@click.option('--arrow-branch', '-b', default=None, - help='Give the branch name explicitly, e.g. master, ARROW-1949.') -@click.option('--arrow-sha', '-t', default=None, - help='Set commit SHA or Tag name explicitly, e.g. f67a515, ' - 'apache-arrow-0.11.1.') -@click.option('--param', '-p', 'params', multiple=True, - help='Additional task parameters for rendering the CI templates') -@click.pass_obj -def render(obj, task, config_path, arrow_version, arrow_remote, arrow_branch, - arrow_sha, params): - """ - Utility command to check the rendered CI templates. - """ - from .core import _flatten - - def highlight(code): - try: - from pygments import highlight - from pygments.lexers import YamlLexer - from pygments.formatters import TerminalFormatter - return highlight(code, YamlLexer(), TerminalFormatter()) - except ImportError: - return code - - arrow = obj['arrow'] - - target = Target.from_repo(arrow, remote=arrow_remote, branch=arrow_branch, - head=arrow_sha, version=arrow_version) - config = Config.load_yaml(config_path) - params = dict([p.split("=") for p in params]) - job = Job.from_config(config=config, target=target, tasks=[task], - params=params) - - for task_name, rendered_files in job.render_tasks().items(): - for path, content in _flatten(rendered_files).items(): - click.echo('#' * 80) - click.echo('### {:^72} ###'.format("/".join(path))) - click.echo('#' * 80) - click.echo(highlight(content)) - - -@crossbow.command() -@click.argument('job-name', required=True) -@click.option('--fetch/--no-fetch', default=True, - help='Fetch references (branches and tags) from the remote') -@click.pass_obj -def status(obj, job_name, fetch): - output = obj['output'] - queue = obj['queue'] - if fetch: - queue.fetch() - job = queue.get(job_name) - ConsoleReport(job).show(output) - - -@crossbow.command() -@click.argument('prefix', required=True) -@click.option('--fetch/--no-fetch', default=True, - help='Fetch references (branches and tags) from the remote') -@click.pass_obj -def latest_prefix(obj, prefix, fetch): - queue = obj['queue'] - if fetch: - queue.fetch() - latest = queue.latest_for_prefix(prefix) - click.echo(latest.branch) - - -@crossbow.command() -@click.argument('job-name', required=True) -@click.option('--sender-name', '-n', - help='Name to use for report e-mail.') -@click.option('--sender-email', '-e', - help='E-mail to use for report e-mail.') -@click.option('--recipient-email', '-r', - help='Where to send the e-mail report') -@click.option('--smtp-user', '-u', - help='E-mail address to use for SMTP login') -@click.option('--smtp-password', '-P', - help='SMTP password to use for report e-mail.') -@click.option('--smtp-server', '-s', default='smtp.gmail.com', - help='SMTP server to use for report e-mail.') -@click.option('--smtp-port', '-p', default=465, - help='SMTP port to use for report e-mail.') -@click.option('--poll/--no-poll', default=False, - help='Wait for completion if there are tasks pending') -@click.option('--poll-max-minutes', default=180, - help='Maximum amount of time waiting for job completion') -@click.option('--poll-interval-minutes', default=10, - help='Number of minutes to wait to check job status again') -@click.option('--send/--dry-run', default=False, - help='Just display the report, don\'t send it') -@click.option('--fetch/--no-fetch', default=True, - help='Fetch references (branches and tags) from the remote') -@click.pass_obj -def report(obj, job_name, sender_name, sender_email, recipient_email, - smtp_user, smtp_password, smtp_server, smtp_port, poll, - poll_max_minutes, poll_interval_minutes, send, fetch): - """ - Send an e-mail report showing success/failure of tasks in a Crossbow run - """ - output = obj['output'] - queue = obj['queue'] - if fetch: - queue.fetch() - - job = queue.get(job_name) - report = EmailReport( - job=job, - sender_name=sender_name, - sender_email=sender_email, - recipient_email=recipient_email - ) - - if poll: - job.wait_until_finished( - poll_max_minutes=poll_max_minutes, - poll_interval_minutes=poll_interval_minutes - ) - - if send: - report.send( - smtp_user=smtp_user, - smtp_password=smtp_password, - smtp_server=smtp_server, - smtp_port=smtp_port - ) - else: - report.show(output) - - -@crossbow.command() -@click.argument('job-name', required=True) -@click.option('-t', '--target-dir', - default=_default_arrow_path / 'packages', - type=click.Path(file_okay=False, dir_okay=True), - help='Directory to download the build artifacts') -@click.option('--dry-run/--execute', default=False, - help='Just display process, don\'t download anything') -@click.option('--fetch/--no-fetch', default=True, - help='Fetch references (branches and tags) from the remote') -@click.pass_obj -def download_artifacts(obj, job_name, target_dir, dry_run, fetch): - """Download build artifacts from GitHub releases""" - output = obj['output'] - - # fetch the queue repository - queue = obj['queue'] - if fetch: - queue.fetch() - - # query the job's artifacts - job = queue.get(job_name) - - # create directory to download the assets to - target_dir = Path(target_dir).absolute() / job_name - target_dir.mkdir(parents=True, exist_ok=True) - - # download the assets while showing the job status - def asset_callback(task_name, task, asset): - if asset is not None: - path = target_dir / task_name / asset.name - path.parent.mkdir(exist_ok=True) - if not dry_run: - asset.download(path) - - click.echo('Downloading {}\'s artifacts.'.format(job_name)) - click.echo('Destination directory is {}'.format(target_dir)) - click.echo() - - report = ConsoleReport(job) - report.show(output, asset_callback=asset_callback) - - -@crossbow.command() -@click.option('--sha', required=True, help='Target committish') -@click.option('--tag', required=True, help='Target tag') -@click.option('--method', default='curl', help='Use cURL to upload') -@click.option('--pattern', '-p', 'patterns', required=True, multiple=True, - help='File pattern to upload as assets') -@click.pass_obj -def upload_artifacts(obj, tag, sha, patterns, method): - queue = obj['queue'] - queue.github_overwrite_release_assets( - tag_name=tag, target_commitish=sha, method=method, patterns=patterns - ) diff --git a/dev/archery/archery/crossbow/core.py b/dev/archery/archery/crossbow/core.py deleted file mode 100644 index 9d3074a21d58..000000000000 --- a/dev/archery/archery/crossbow/core.py +++ /dev/null @@ -1,1162 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -import os -import re -import fnmatch -import glob -import time -import logging -import mimetypes -import subprocess -import textwrap -from io import StringIO -from pathlib import Path -from datetime import date - -import jinja2 -from ruamel.yaml import YAML - -try: - import github3 - _have_github3 = True -except ImportError: - github3 = object - _have_github3 = False - -try: - import pygit2 -except ImportError: - PygitRemoteCallbacks = object -else: - PygitRemoteCallbacks = pygit2.RemoteCallbacks - -from ..utils.source import ArrowSources - - -for pkg in ["requests", "urllib3", "github3"]: - logging.getLogger(pkg).setLevel(logging.WARNING) - -logger = logging.getLogger("crossbow") - - -class CrossbowError(Exception): - pass - - -def _flatten(mapping): - """Converts a hierarchical mapping to a flat dictionary""" - result = {} - for k, v in mapping.items(): - if isinstance(v, dict): - for ik, iv in _flatten(v).items(): - ik = ik if isinstance(ik, tuple) else (ik,) - result[(k,) + ik] = iv - elif isinstance(v, list): - for ik, iv in enumerate(_flatten(v)): - ik = ik if isinstance(ik, tuple) else (ik,) - result[(k,) + ik] = iv - else: - result[(k,)] = v - return result - - -def _unflatten(mapping): - """Converts a flat tuple => object mapping to hierarchical one""" - result = {} - for path, value in mapping.items(): - parents, leaf = path[:-1], path[-1] - # create the hierarchy until we reach the leaf value - temp = result - for parent in parents: - temp.setdefault(parent, {}) - temp = temp[parent] - # set the leaf value - temp[leaf] = value - - return result - - -def _unflatten_tree(files): - """Converts a flat path => object mapping to a hierarchical directories - - Input: - { - 'path/to/file.a': a_content, - 'path/to/file.b': b_content, - 'path/file.c': c_content - } - Output: - { - 'path': { - 'to': { - 'file.a': a_content, - 'file.b': b_content - }, - 'file.c': c_content - } - } - """ - files = {tuple(k.split('/')): v for k, v in files.items()} - return _unflatten(files) - - -def _render_jinja_template(searchpath, template, params): - def format_all(items, pattern): - return [pattern.format(item) for item in items] - - loader = jinja2.FileSystemLoader(searchpath) - env = jinja2.Environment(loader=loader, trim_blocks=True, - lstrip_blocks=True, - undefined=jinja2.StrictUndefined) - env.filters['format_all'] = format_all - template = env.get_template(template) - return template.render(**params) - - -# configurations for setting up branch skipping -# - appveyor has a feature to skip builds without an appveyor.yml -# - travis reads from the master branch and applies the rules -# - circle requires the configuration to be present on all branch, even ones -# that are configured to be skipped -# - azure skips branches without azure-pipelines.yml by default -# - github skips branches without .github/workflows/ by default - -_default_travis_yml = """ -branches: - only: - - master - - /.*-travis-.*/ - -os: linux -dist: trusty -language: generic -""" - -_default_circle_yml = """ -version: 2 - -jobs: - build: - machine: true - -workflows: - version: 2 - build: - jobs: - - build: - filters: - branches: - only: - - /.*-circle-.*/ -""" - -_default_tree = { - '.travis.yml': _default_travis_yml, - '.circleci/config.yml': _default_circle_yml -} - - -class GitRemoteCallbacks(PygitRemoteCallbacks): - - def __init__(self, token): - self.token = token - self.attempts = 0 - super().__init__() - - def push_update_reference(self, refname, message): - pass - - def update_tips(self, refname, old, new): - pass - - def credentials(self, url, username_from_url, allowed_types): - # its a libgit2 bug, that it infinitely retries the authentication - self.attempts += 1 - - if self.attempts >= 5: - # pygit2 doesn't propagate the exception properly - msg = 'Wrong oauth personal access token' - print(msg) - raise CrossbowError(msg) - - if allowed_types & pygit2.credentials.GIT_CREDTYPE_USERPASS_PLAINTEXT: - return pygit2.UserPass(self.token, 'x-oauth-basic') - else: - return None - - -def _git_ssh_to_https(url): - return url.replace('git@github.com:', 'https://github.com/') - - -class Repo: - """ - Base class for interaction with local git repositories - - A high level wrapper used for both reading revision information from - arrow's repository and pushing continuous integration tasks to the queue - repository. - - Parameters - ---------- - require_https : boolean, default False - Raise exception for SSH origin URLs - """ - - def __init__(self, path, github_token=None, remote_url=None, - require_https=False): - self.path = Path(path) - self.github_token = github_token - self.require_https = require_https - self._remote_url = remote_url - self._pygit_repo = None - self._github_repo = None # set by as_github_repo() - self._updated_refs = [] - - def __str__(self): - tpl = textwrap.dedent(''' - Repo: {remote}@{branch} - Commit: {head} - ''') - return tpl.format( - remote=self.remote_url, - branch=self.branch.branch_name, - head=self.head - ) - - @property - def repo(self): - if self._pygit_repo is None: - self._pygit_repo = pygit2.Repository(str(self.path)) - return self._pygit_repo - - @property - def origin(self): - remote = self.repo.remotes['origin'] - if self.require_https and remote.url.startswith('git@github.com'): - raise CrossbowError("Change SSH origin URL to HTTPS to use " - "Crossbow: {}".format(remote.url)) - return remote - - def fetch(self): - refspec = '+refs/heads/*:refs/remotes/origin/*' - self.origin.fetch([refspec]) - - def push(self, refs=None, github_token=None): - github_token = github_token or self.github_token - if github_token is None: - raise RuntimeError( - 'Could not determine GitHub token. Please set the ' - 'CROSSBOW_GITHUB_TOKEN environment variable to a ' - 'valid GitHub access token or pass one to --github-token.' - ) - callbacks = GitRemoteCallbacks(github_token) - refs = refs or [] - try: - self.origin.push(refs + self._updated_refs, callbacks=callbacks) - except pygit2.GitError: - raise RuntimeError('Failed to push updated references, ' - 'potentially because of credential issues: {}' - .format(self._updated_refs)) - else: - self.updated_refs = [] - - @property - def head(self): - """Currently checked out commit's sha""" - return self.repo.head - - @property - def branch(self): - """Currently checked out branch""" - try: - return self.repo.branches[self.repo.head.shorthand] - except KeyError: - return None # detached - - @property - def remote(self): - """Currently checked out branch's remote counterpart""" - try: - return self.repo.remotes[self.branch.upstream.remote_name] - except (AttributeError, KeyError): - return None # cannot detect - - @property - def remote_url(self): - """Currently checked out branch's remote counterpart URL - - If an SSH github url is set, it will be replaced by the https - equivalent usable with GitHub OAuth token. - """ - try: - return self._remote_url or _git_ssh_to_https(self.remote.url) - except AttributeError: - return None - - @property - def user_name(self): - try: - return next(self.repo.config.get_multivar('user.name')) - except StopIteration: - return os.environ.get('GIT_COMMITTER_NAME', 'unknown') - - @property - def user_email(self): - try: - return next(self.repo.config.get_multivar('user.email')) - except StopIteration: - return os.environ.get('GIT_COMMITTER_EMAIL', 'unknown') - - @property - def signature(self): - return pygit2.Signature(self.user_name, self.user_email, - int(time.time())) - - def create_tree(self, files): - builder = self.repo.TreeBuilder() - - for filename, content in files.items(): - if isinstance(content, dict): - # create a subtree - tree_id = self.create_tree(content) - builder.insert(filename, tree_id, pygit2.GIT_FILEMODE_TREE) - else: - # create a file - blob_id = self.repo.create_blob(content) - builder.insert(filename, blob_id, pygit2.GIT_FILEMODE_BLOB) - - tree_id = builder.write() - return tree_id - - def create_commit(self, files, parents=None, message='', - reference_name=None): - if parents is None: - # by default use the main branch as the base of the new branch - # required to reuse github actions cache across crossbow tasks - commit, _ = self.repo.resolve_refish("master") - parents = [commit.id] - tree_id = self.create_tree(files) - - author = committer = self.signature - commit_id = self.repo.create_commit(reference_name, author, committer, - message, tree_id, parents) - return self.repo[commit_id] - - def create_branch(self, branch_name, files, parents=None, message='', - signature=None): - # create commit with the passed tree - commit = self.create_commit(files, parents=parents, message=message) - - # create branch pointing to the previously created commit - branch = self.repo.create_branch(branch_name, commit) - - # append to the pushable references - self._updated_refs.append('refs/heads/{}'.format(branch_name)) - - return branch - - def create_tag(self, tag_name, commit_id, message=''): - tag_id = self.repo.create_tag(tag_name, commit_id, - pygit2.GIT_OBJ_COMMIT, self.signature, - message) - - # append to the pushable references - self._updated_refs.append('refs/tags/{}'.format(tag_name)) - - return self.repo[tag_id] - - def file_contents(self, commit_id, file): - commit = self.repo[commit_id] - entry = commit.tree[file] - blob = self.repo[entry.id] - return blob.data - - def _parse_github_user_repo(self): - m = re.match(r'.*\/([^\/]+)\/([^\/\.]+)(\.git)?$', self.remote_url) - if m is None: - raise CrossbowError( - "Unable to parse the github owner and repository from the " - "repository's remote url '{}'".format(self.remote_url) - ) - user, repo = m.group(1), m.group(2) - return user, repo - - def as_github_repo(self, github_token=None): - """Converts it to a repository object which wraps the GitHub API""" - if self._github_repo is None: - if not _have_github3: - raise ImportError('Must install github3.py') - github_token = github_token or self.github_token - username, reponame = self._parse_github_user_repo() - session = github3.session.GitHubSession( - default_connect_timeout=10, - default_read_timeout=30 - ) - github = github3.GitHub(session=session) - github.login(token=github_token) - self._github_repo = github.repository(username, reponame) - return self._github_repo - - def github_commit(self, sha): - repo = self.as_github_repo() - return repo.commit(sha) - - def github_release(self, tag): - repo = self.as_github_repo() - try: - return repo.release_from_tag(tag) - except github3.exceptions.NotFoundError: - return None - - def github_upload_asset_requests(self, release, path, name, mime, - max_retries=None, retry_backoff=None): - if max_retries is None: - max_retries = int(os.environ.get('CROSSBOW_MAX_RETRIES', 8)) - if retry_backoff is None: - retry_backoff = int(os.environ.get('CROSSBOW_RETRY_BACKOFF', 5)) - - for i in range(max_retries): - try: - with open(path, 'rb') as fp: - result = release.upload_asset(name=name, asset=fp, - content_type=mime) - except github3.exceptions.ResponseError as e: - logger.error('Attempt {} has failed with message: {}.' - .format(i + 1, str(e))) - logger.error('Error message {}'.format(e.msg)) - logger.error('List of errors provided by Github:') - for err in e.errors: - logger.error(' - {}'.format(err)) - - if e.code == 422: - # 422 Validation Failed, probably raised because - # ReleaseAsset already exists, so try to remove it before - # reattempting the asset upload - for asset in release.assets(): - if asset.name == name: - logger.info('Release asset {} already exists, ' - 'removing it...'.format(name)) - asset.delete() - logger.info('Asset {} removed.'.format(name)) - break - except github3.exceptions.ConnectionError as e: - logger.error('Attempt {} has failed with message: {}.' - .format(i + 1, str(e))) - else: - logger.info('Attempt {} has finished.'.format(i + 1)) - return result - - time.sleep(retry_backoff) - - raise RuntimeError('Github asset uploading has failed!') - - def github_upload_asset_curl(self, release, path, name, mime): - upload_url, _ = release.upload_url.split('{?') - upload_url += '?name={}'.format(name) - - command = [ - 'curl', - '--fail', - '-H', "Authorization: token {}".format(self.github_token), - '-H', "Content-Type: {}".format(mime), - '--data-binary', '@{}'.format(path), - upload_url - ] - return subprocess.run(command, shell=False, check=True) - - def github_overwrite_release_assets(self, tag_name, target_commitish, - patterns, method='requests'): - # Since github has changed something the asset uploading via requests - # got instable, so prefer the cURL alternative. - # Potential cause: - # sigmavirus24/github3.py/issues/779#issuecomment-379470626 - repo = self.as_github_repo() - if not tag_name: - raise CrossbowError('Empty tag name') - if not target_commitish: - raise CrossbowError('Empty target commit for the release tag') - - # remove the whole release if it already exists - try: - release = repo.release_from_tag(tag_name) - except github3.exceptions.NotFoundError: - pass - else: - release.delete() - - release = repo.create_release(tag_name, target_commitish) - for pattern in patterns: - for path in glob.glob(pattern, recursive=True): - name = os.path.basename(path) - size = os.path.getsize(path) - mime = mimetypes.guess_type(name)[0] or 'application/zip' - - logger.info( - 'Uploading asset `{}` with mimetype {} and size {}...' - .format(name, mime, size) - ) - - if method == 'requests': - self.github_upload_asset_requests(release, path, name=name, - mime=mime) - elif method == 'curl': - self.github_upload_asset_curl(release, path, name=name, - mime=mime) - else: - raise CrossbowError( - 'Unsupported upload method {}'.format(method) - ) - - -class Queue(Repo): - - def _latest_prefix_id(self, prefix): - pattern = re.compile(r'[\w\/-]*{}-(\d+)'.format(prefix)) - matches = list(filter(None, map(pattern.match, self.repo.branches))) - if matches: - latest = max(int(m.group(1)) for m in matches) - else: - latest = -1 - return latest - - def _next_job_id(self, prefix): - """Auto increments the branch's identifier based on the prefix""" - latest_id = self._latest_prefix_id(prefix) - return '{}-{}'.format(prefix, latest_id + 1) - - def latest_for_prefix(self, prefix): - latest_id = self._latest_prefix_id(prefix) - if latest_id < 0: - raise RuntimeError( - 'No job has been submitted with prefix {} yet'.format(prefix) - ) - job_name = '{}-{}'.format(prefix, latest_id) - return self.get(job_name) - - def date_of(self, job): - # it'd be better to bound to the queue repository on deserialization - # and reorganize these methods to Job - branch_name = 'origin/{}'.format(job.branch) - branch = self.repo.branches[branch_name] - commit = self.repo[branch.target] - return date.fromtimestamp(commit.commit_time) - - def jobs(self, pattern): - """Return jobs sorted by its identifier in reverse order""" - job_names = [] - for name in self.repo.branches.remote: - origin, name = name.split('/', 1) - result = re.match(pattern, name) - if result: - job_names.append(name) - - for name in sorted(job_names, reverse=True): - yield self.get(name) - - def get(self, job_name): - branch_name = 'origin/{}'.format(job_name) - branch = self.repo.branches[branch_name] - try: - content = self.file_contents(branch.target, 'job.yml') - except KeyError: - raise CrossbowError( - 'No job is found with name: {}'.format(job_name) - ) - - buffer = StringIO(content.decode('utf-8')) - job = yaml.load(buffer) - job.queue = self - return job - - def put(self, job, prefix='build'): - if not isinstance(job, Job): - raise CrossbowError('`job` must be an instance of Job') - if job.branch is not None: - raise CrossbowError('`job.branch` is automatically generated, ' - 'thus it must be blank') - - if job.target.remote is None: - raise CrossbowError( - 'Cannot determine git remote for the Arrow repository to ' - 'clone or push to, try to push the `{}` branch first to have ' - 'a remote tracking counterpart.'.format(job.target.branch) - ) - if job.target.branch is None: - raise CrossbowError( - 'Cannot determine the current branch of the Arrow repository ' - 'to clone or push to, perhaps it is in detached HEAD state. ' - 'Please checkout a branch.' - ) - - # auto increment and set next job id, e.g. build-85 - job._queue = self - job.branch = self._next_job_id(prefix) - - # create tasks' branches - for task_name, task in job.tasks.items(): - # adding CI's name to the end of the branch in order to use skip - # patterns on travis and circleci - task.branch = '{}-{}-{}'.format(job.branch, task.ci, task_name) - params = { - **job.params, - "arrow": job.target, - "queue_remote_url": self.remote_url - } - files = task.render_files(job.template_searchpath, params=params) - branch = self.create_branch(task.branch, files=files) - self.create_tag(task.tag, branch.target) - task.commit = str(branch.target) - - # create job's branch with its description - return self.create_branch(job.branch, files=job.render_files()) - - -def get_version(root, **kwargs): - """ - Parse function for setuptools_scm that ignores tags for non-C++ - subprojects, e.g. apache-arrow-js-XXX tags. - """ - from setuptools_scm.git import parse as parse_git_version - - # query the calculated version based on the git tags - kwargs['describe_command'] = ( - 'git describe --dirty --tags --long --match "apache-arrow-[0-9].*"' - ) - version = parse_git_version(root, **kwargs) - - # increment the minor version, because there can be patch releases created - # from maintenance branches where the tags are unreachable from the - # master's HEAD, so the git command above generates 0.17.0.dev300 even if - # arrow has a never 0.17.1 patch release - pattern = r"^(\d+)\.(\d+)\.(\d+)$" - match = re.match(pattern, str(version.tag)) - major, minor, patch = map(int, match.groups()) - - # the bumped version number after 0.17.x will be 0.18.0.dev300 - return "{}.{}.{}.dev{}".format(major, minor + 1, patch, version.distance) - - -class Serializable: - - @classmethod - def to_yaml(cls, representer, data): - tag = '!{}'.format(cls.__name__) - dct = {k: v for k, v in data.__dict__.items() if not k.startswith('_')} - return representer.represent_mapping(tag, dct) - - -class Target(Serializable): - """ - Describes target repository and revision the builds run against - - This serializable data container holding information about arrow's - git remote, branch, sha and version number as well as some metadata - (currently only an email address where the notification should be sent). - """ - - def __init__(self, head, branch, remote, version, email=None): - self.head = head - self.email = email - self.branch = branch - self.remote = remote - self.version = version - self.no_rc_version = re.sub(r'-rc\d+\Z', '', version) - # Semantic Versioning 1.0.0: https://semver.org/spec/v1.0.0.html - # - # > A pre-release version number MAY be denoted by appending an - # > arbitrary string immediately following the patch version and a - # > dash. The string MUST be comprised of only alphanumerics plus - # > dash [0-9A-Za-z-]. - # - # Example: - # - # '0.16.1.dev10' -> - # '0.16.1-dev10' - self.no_rc_semver_version = \ - re.sub(r'\.(dev\d+)\Z', r'-\1', self.no_rc_version) - - @classmethod - def from_repo(cls, repo, head=None, branch=None, remote=None, version=None, - email=None): - """Initialize from a repository - - Optionally override detected remote, branch, head, and/or version. - """ - assert isinstance(repo, Repo) - - if head is None: - head = str(repo.head.target) - if branch is None: - branch = repo.branch.branch_name - if remote is None: - remote = repo.remote_url - if version is None: - version = get_version(repo.path) - if email is None: - email = repo.user_email - - return cls(head=head, email=email, branch=branch, remote=remote, - version=version) - - -class Task(Serializable): - """ - Describes a build task and metadata required to render CI templates - - A task is represented as a single git commit and branch containing jinja2 - rendered files (currently appveyor.yml or .travis.yml configurations). - - A task can't be directly submitted to a queue, must belong to a job. - Each task's unique identifier is its branch name, which is generated after - submitting the job to a queue. - """ - - def __init__(self, ci, template, artifacts=None, params=None): - assert ci in { - 'circle', - 'travis', - 'appveyor', - 'azure', - 'github', - 'drone', - } - self.ci = ci - self.template = template - self.artifacts = artifacts or [] - self.params = params or {} - self.branch = None # filled after adding to a queue - self.commit = None # filled after adding to a queue - self._queue = None # set by the queue object after put or get - self._status = None # status cache - self._assets = None # assets cache - - def render_files(self, searchpath, params=None): - params = {**self.params, **(params or {}), "task": self} - try: - rendered = _render_jinja_template(searchpath, self.template, - params=params) - except jinja2.TemplateError as e: - raise RuntimeError( - 'Failed to render template `{}` with {}: {}'.format( - self.template, e.__class__.__name__, str(e) - ) - ) - - tree = {**_default_tree, self.filename: rendered} - return _unflatten_tree(tree) - - @property - def tag(self): - return self.branch - - @property - def filename(self): - config_files = { - 'circle': '.circleci/config.yml', - 'travis': '.travis.yml', - 'appveyor': 'appveyor.yml', - 'azure': 'azure-pipelines.yml', - 'github': '.github/workflows/crossbow.yml', - 'drone': '.drone.yml', - } - return config_files[self.ci] - - def status(self, force_query=False): - _status = getattr(self, '_status', None) - if force_query or _status is None: - github_commit = self._queue.github_commit(self.commit) - self._status = TaskStatus(github_commit) - return self._status - - def assets(self, force_query=False): - _assets = getattr(self, '_assets', None) - if force_query or _assets is None: - github_release = self._queue.github_release(self.tag) - self._assets = TaskAssets(github_release, - artifact_patterns=self.artifacts) - return self._assets - - -class TaskStatus: - """ - Combine the results from status and checks API to a single state. - - Azure pipelines uses checks API which doesn't provide a combined - interface like status API does, so we need to manually combine - both the commit statuses and the commit checks coming from - different API endpoint - - Status.state: error, failure, pending or success, default pending - CheckRun.status: queued, in_progress or completed, default: queued - CheckRun.conclusion: success, failure, neutral, cancelled, timed_out - or action_required, only set if - CheckRun.status == 'completed' - - 1. Convert CheckRun's status and conclusion to one of Status.state - 2. Merge the states based on the following rules: - - failure if any of the contexts report as error or failure - - pending if there are no statuses or a context is pending - - success if the latest status for all contexts is success - error otherwise. - - Parameters - ---------- - commit : github3.Commit - Commit to query the combined status for. - - Returns - ------- - TaskStatus( - combined_state='error|failure|pending|success', - github_status='original github status object', - github_check_runs='github checks associated with the commit', - total_count='number of statuses and checks' - ) - """ - - def __init__(self, commit): - status = commit.status() - check_runs = list(commit.check_runs()) - states = [s.state for s in status.statuses] - - for check in check_runs: - if check.status == 'completed': - if check.conclusion in {'success', 'failure'}: - states.append(check.conclusion) - elif check.conclusion in {'cancelled', 'timed_out', - 'action_required'}: - states.append('error') - # omit `neutral` conclusion - else: - states.append('pending') - - # it could be more effective, but the following is more descriptive - combined_state = 'error' - if len(states): - if any(state in {'error', 'failure'} for state in states): - combined_state = 'failure' - elif any(state == 'pending' for state in states): - combined_state = 'pending' - elif all(state == 'success' for state in states): - combined_state = 'success' - - # show link to the actual build, some of the CI providers implement - # the statuses API others implement the checks API, so display both - build_links = [s.target_url for s in status.statuses] - build_links += [c.html_url for c in check_runs] - - self.combined_state = combined_state - self.github_status = status - self.github_check_runs = check_runs - self.total_count = len(states) - self.build_links = build_links - - -class TaskAssets(dict): - - def __init__(self, github_release, artifact_patterns): - # HACK(kszucs): don't expect uploaded assets of no atifacts were - # defiened for the tasks in order to spare a bit of github rate limit - if not artifact_patterns: - return - - if github_release is None: - github_assets = {} # no assets have been uploaded for the task - else: - github_assets = {a.name: a for a in github_release.assets()} - - for pattern in artifact_patterns: - # artifact can be a regex pattern - compiled = re.compile(pattern) - matches = list( - filter(None, map(compiled.match, github_assets.keys())) - ) - num_matches = len(matches) - - # validate artifact pattern matches single asset - if num_matches == 0: - self[pattern] = None - elif num_matches == 1: - self[pattern] = github_assets[matches[0].group(0)] - else: - raise CrossbowError( - 'Only a single asset should match pattern `{}`, there are ' - 'multiple ones: {}'.format(pattern, ', '.join(matches)) - ) - - def missing_patterns(self): - return [pattern for pattern, asset in self.items() if asset is None] - - def uploaded_assets(self): - return [asset for asset in self.values() if asset is not None] - - -class Job(Serializable): - """Describes multiple tasks against a single target repository""" - - def __init__(self, target, tasks, params=None, template_searchpath=None): - if not tasks: - raise ValueError('no tasks were provided for the job') - if not all(isinstance(task, Task) for task in tasks.values()): - raise ValueError('each `tasks` mus be an instance of Task') - if not isinstance(target, Target): - raise ValueError('`target` must be an instance of Target') - if not isinstance(target, Target): - raise ValueError('`target` must be an instance of Target') - if not isinstance(params, dict): - raise ValueError('`params` must be an instance of dict') - - self.target = target - self.tasks = tasks - self.params = params or {} # additional parameters for the tasks - self.branch = None # filled after adding to a queue - self._queue = None # set by the queue object after put or get - if template_searchpath is None: - self._template_searchpath = ArrowSources.find().path - else: - self._template_searchpath = template_searchpath - - def render_files(self): - with StringIO() as buf: - yaml.dump(self, buf) - content = buf.getvalue() - tree = {**_default_tree, "job.yml": content} - return _unflatten_tree(tree) - - def render_tasks(self, params=None): - result = {} - params = { - **self.params, - "arrow": self.target, - **(params or {}) - } - for task_name, task in self.tasks.items(): - files = task.render_files(self._template_searchpath, params) - result[task_name] = files - return result - - @property - def template_searchpath(self): - return self._template_searchpath - - @property - def queue(self): - assert isinstance(self._queue, Queue) - return self._queue - - @queue.setter - def queue(self, queue): - assert isinstance(queue, Queue) - self._queue = queue - for task in self.tasks.values(): - task._queue = queue - - @property - def email(self): - return os.environ.get('CROSSBOW_EMAIL', self.target.email) - - @property - def date(self): - return self.queue.date_of(self) - - def show(self, stream=None): - return yaml.dump(self, stream=stream) - - @classmethod - def from_config(cls, config, target, tasks=None, groups=None, params=None): - """ - Intantiate a job from based on a config. - - Parameters - ---------- - config : dict - Deserialized content of tasks.yml - target : Target - Describes target repository and revision the builds run against. - tasks : Optional[List[str]], default None - List of glob patterns for matching task names. - groups : Optional[List[str]], default None - List of exact group names matching predefined task sets in the - config. - params : Optional[Dict[str, str]], default None - Additional rendering parameters for the task templates. - - Returns - ------- - Job - - Raises - ------ - Exception: - If invalid groups or tasks has been passed. - """ - task_definitions = config.select(tasks, groups=groups) - - # instantiate the tasks - tasks = {} - versions = {'version': target.version, - 'no_rc_version': target.no_rc_version, - 'no_rc_semver_version': target.no_rc_semver_version} - for task_name, task in task_definitions.items(): - artifacts = task.pop('artifacts', None) or [] # because of yaml - artifacts = [fn.format(**versions) for fn in artifacts] - tasks[task_name] = Task(artifacts=artifacts, **task) - - return cls(target=target, tasks=tasks, params=params, - template_searchpath=config.template_searchpath) - - def is_finished(self): - for task in self.tasks.values(): - status = task.status(force_query=True) - if status.combined_state == 'pending': - return False - return True - - def wait_until_finished(self, poll_max_minutes=120, - poll_interval_minutes=10): - started_at = time.time() - while True: - if self.is_finished(): - break - - waited_for_minutes = (time.time() - started_at) / 60 - if waited_for_minutes > poll_max_minutes: - msg = ('Exceeded the maximum amount of time waiting for job ' - 'to finish, waited for {} minutes.') - raise RuntimeError(msg.format(waited_for_minutes)) - - logger.info('Waiting {} minutes and then checking again' - .format(poll_interval_minutes)) - time.sleep(poll_interval_minutes * 60) - - -class Config(dict): - - def __init__(self, tasks, template_searchpath): - super().__init__(tasks) - self.template_searchpath = template_searchpath - - @classmethod - def load_yaml(cls, path): - path = Path(path) - searchpath = path.parent - rendered = _render_jinja_template(searchpath, template=path.name, - params={}) - config = yaml.load(rendered) - return cls(config, template_searchpath=searchpath) - - def show(self, stream=None): - return yaml.dump(dict(self), stream=stream) - - def select(self, tasks=None, groups=None): - config_groups = dict(self['groups']) - config_tasks = dict(self['tasks']) - valid_groups = set(config_groups.keys()) - valid_tasks = set(config_tasks.keys()) - group_whitelist = list(groups or []) - task_whitelist = list(tasks or []) - - # validate that the passed groups are defined in the config - requested_groups = set(group_whitelist) - invalid_groups = requested_groups - valid_groups - if invalid_groups: - msg = 'Invalid group(s) {!r}. Must be one of {!r}'.format( - invalid_groups, valid_groups - ) - raise CrossbowError(msg) - - # merge the tasks defined in the selected groups - task_patterns = [list(config_groups[name]) for name in group_whitelist] - task_patterns = set(sum(task_patterns, task_whitelist)) - - # treat the task names as glob patterns to select tasks more easily - requested_tasks = set() - for pattern in task_patterns: - matches = fnmatch.filter(valid_tasks, pattern) - if len(matches): - requested_tasks.update(matches) - else: - raise CrossbowError( - "Unable to match any tasks for `{}`".format(pattern) - ) - - # validate that the passed and matched tasks are defined in the config - invalid_tasks = requested_tasks - valid_tasks - if invalid_tasks: - msg = 'Invalid task(s) {!r}. Must be one of {!r}'.format( - invalid_tasks, valid_tasks - ) - raise CrossbowError(msg) - - return { - task_name: config_tasks[task_name] for task_name in requested_tasks - } - - def validate(self): - # validate that the task groups are properly referening the tasks - for group_name, group in self['groups'].items(): - for pattern in group: - tasks = self.select(tasks=[pattern]) - if not tasks: - raise CrossbowError( - "The pattern `{}` defined for task group `{}` is not " - "matching any of the tasks defined in the " - "configuration file.".format(pattern, group_name) - ) - - # validate that the tasks are constructible - for task_name, task in self['tasks'].items(): - try: - Task(**task) - except Exception as e: - raise CrossbowError( - 'Unable to construct a task object from the ' - 'definition of task `{}`. The original error message ' - 'is: `{}`'.format(task_name, str(e)) - ) - - # validate that the defined tasks are renderable, in order to to that - # define the required object with dummy data - target = Target( - head='e279a7e06e61c14868ca7d71dea795420aea6539', - branch='master', - remote='https://github.com/apache/arrow', - version='1.0.0dev123', - email='dummy@example.ltd' - ) - - for task_name, task in self['tasks'].items(): - task = Task(**task) - files = task.render_files( - self.template_searchpath, - params=dict( - arrow=target, - queue_remote_url='https://github.com/org/crossbow' - ) - ) - if not files: - raise CrossbowError('No files have been rendered for task `{}`' - .format(task_name)) - - -# configure yaml serializer -yaml = YAML() -yaml.register_class(Job) -yaml.register_class(Task) -yaml.register_class(Target) diff --git a/dev/archery/archery/crossbow/reports.py b/dev/archery/archery/crossbow/reports.py deleted file mode 100644 index bc82db7f51a5..000000000000 --- a/dev/archery/archery/crossbow/reports.py +++ /dev/null @@ -1,302 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -import click -import collections -import operator -import functools -from io import StringIO -import textwrap - - -# TODO(kszucs): use archery.report.JinjaReport instead -class Report: - - def __init__(self, job): - self.job = job - - def show(self): - raise NotImplementedError() - - -class ConsoleReport(Report): - """Report the status of a Job to the console using click""" - - # output table's header template - HEADER = '[{state:>7}] {branch:<52} {content:>16}' - DETAILS = ' └ {url}' - - # output table's row template for assets - ARTIFACT_NAME = '{artifact:>69} ' - ARTIFACT_STATE = '[{state:>7}]' - - # state color mapping to highlight console output - COLORS = { - # from CombinedStatus - 'error': 'red', - 'failure': 'red', - 'pending': 'yellow', - 'success': 'green', - # custom state messages - 'ok': 'green', - 'missing': 'red' - } - - def lead(self, state, branch, n_uploaded, n_expected): - line = self.HEADER.format( - state=state.upper(), - branch=branch, - content='uploaded {} / {}'.format(n_uploaded, n_expected) - ) - return click.style(line, fg=self.COLORS[state.lower()]) - - def header(self): - header = self.HEADER.format( - state='state', - branch='Task / Branch', - content='Artifacts' - ) - delimiter = '-' * len(header) - return '{}\n{}'.format(header, delimiter) - - def artifact(self, state, pattern, asset): - if asset is None: - artifact = pattern - state = 'pending' if state == 'pending' else 'missing' - else: - artifact = asset.name - state = 'ok' - - name_ = self.ARTIFACT_NAME.format(artifact=artifact) - state_ = click.style( - self.ARTIFACT_STATE.format(state=state.upper()), - self.COLORS[state] - ) - return name_ + state_ - - def show(self, outstream, asset_callback=None): - echo = functools.partial(click.echo, file=outstream) - - # write table's header - echo(self.header()) - - # write table's body - for task_name, task in sorted(self.job.tasks.items()): - # if not task_name.startswith("test-debian-10-python-3"): - # continue - # write summary of the uploaded vs total assets - status = task.status() - assets = task.assets() - - # mapping of artifact pattern to asset or None of not uploaded - n_expected = len(task.artifacts) - n_uploaded = len(assets.uploaded_assets()) - echo(self.lead(status.combined_state, task_name, n_uploaded, - n_expected)) - - # show link to the actual build, some of the CI providers implement - # the statuses API others implement the checks API, so display both - for link in status.build_links: - echo(self.DETAILS.format(url=link)) - - # write per asset status - for artifact_pattern, asset in assets.items(): - if asset_callback is not None: - asset_callback(task_name, task, asset) - echo(self.artifact(status.combined_state, artifact_pattern, - asset)) - - -class EmailReport(Report): - - HEADER = textwrap.dedent(""" - Arrow Build Report for Job {job_name} - - All tasks: {all_tasks_url} - """) - - TASK = textwrap.dedent(""" - - {name}: - URL: {url} - """).strip() - - EMAIL = textwrap.dedent(""" - From: {sender_name} <{sender_email}> - To: {recipient_email} - Subject: {subject} - - {body} - """).strip() - - STATUS_HEADERS = { - # from CombinedStatus - 'error': 'Errored Tasks:', - 'failure': 'Failed Tasks:', - 'pending': 'Pending Tasks:', - 'success': 'Succeeded Tasks:', - } - - def __init__(self, job, sender_name, sender_email, recipient_email): - self.sender_name = sender_name - self.sender_email = sender_email - self.recipient_email = recipient_email - super().__init__(job) - - def url(self, query): - repo_url = self.job.queue.remote_url.strip('.git') - return '{}/branches/all?query={}'.format(repo_url, query) - - def listing(self, tasks): - return '\n'.join( - sorted( - self.TASK.format(name=task_name, url=self.url(task.branch)) - for task_name, task in tasks.items() - ) - ) - - def header(self): - url = self.url(self.job.branch) - return self.HEADER.format(job_name=self.job.branch, all_tasks_url=url) - - def subject(self): - return ( - "[NIGHTLY] Arrow Build Report for Job {}".format(self.job.branch) - ) - - def body(self): - buffer = StringIO() - buffer.write(self.header()) - - tasks_by_state = collections.defaultdict(dict) - for task_name, task in self.job.tasks.items(): - state = task.status().combined_state - tasks_by_state[state][task_name] = task - - for state in ('failure', 'error', 'pending', 'success'): - if state in tasks_by_state: - tasks = tasks_by_state[state] - buffer.write('\n') - buffer.write(self.STATUS_HEADERS[state]) - buffer.write('\n') - buffer.write(self.listing(tasks)) - buffer.write('\n') - - return buffer.getvalue() - - def email(self): - return self.EMAIL.format( - sender_name=self.sender_name, - sender_email=self.sender_email, - recipient_email=self.recipient_email, - subject=self.subject(), - body=self.body() - ) - - def show(self, outstream): - outstream.write(self.email()) - - def send(self, smtp_user, smtp_password, smtp_server, smtp_port): - import smtplib - - email = self.email() - - server = smtplib.SMTP_SSL(smtp_server, smtp_port) - server.ehlo() - server.login(smtp_user, smtp_password) - server.sendmail(smtp_user, self.recipient_email, email) - server.close() - - -class CommentReport(Report): - - _markdown_badge = '[![{title}]({badge})]({url})' - - badges = { - 'github': _markdown_badge.format( - title='Github Actions', - url='https://github.com/{repo}/actions?query=branch:{branch}', - badge=( - 'https://github.com/{repo}/workflows/Crossbow/' - 'badge.svg?branch={branch}' - ), - ), - 'azure': _markdown_badge.format( - title='Azure', - url=( - 'https://dev.azure.com/{repo}/_build/latest' - '?definitionId=1&branchName={branch}' - ), - badge=( - 'https://dev.azure.com/{repo}/_apis/build/status/' - '{repo_dotted}?branchName={branch}' - ) - ), - 'travis': _markdown_badge.format( - title='TravisCI', - url='https://travis-ci.com/{repo}/branches', - badge='https://img.shields.io/travis/{repo}/{branch}.svg' - ), - 'circle': _markdown_badge.format( - title='CircleCI', - url='https://circleci.com/gh/{repo}/tree/{branch}', - badge=( - 'https://img.shields.io/circleci/build/github' - '/{repo}/{branch}.svg' - ) - ), - 'appveyor': _markdown_badge.format( - title='Appveyor', - url='https://ci.appveyor.com/project/{repo}/history', - badge='https://img.shields.io/appveyor/ci/{repo}/{branch}.svg' - ), - 'drone': _markdown_badge.format( - title='Drone', - url='https://cloud.drone.io/{repo}', - badge='https://img.shields.io/drone/build/{repo}/{branch}.svg' - ), - } - - def __init__(self, job, crossbow_repo): - self.crossbow_repo = crossbow_repo - super().__init__(job) - - def show(self): - url = 'https://github.com/{repo}/branches/all?query={branch}' - sha = self.job.target.head - - msg = 'Revision: {}\n\n'.format(sha) - msg += 'Submitted crossbow builds: [{repo} @ {branch}]' - msg += '({})\n'.format(url) - msg += '\n|Task|Status|\n|----|------|' - - tasks = sorted(self.job.tasks.items(), key=operator.itemgetter(0)) - for key, task in tasks: - branch = task.branch - - try: - template = self.badges[task.ci] - badge = template.format( - repo=self.crossbow_repo, - repo_dotted=self.crossbow_repo.replace('/', '.'), - branch=branch - ) - except KeyError: - badge = 'unsupported CI service `{}`'.format(task.ci) - - msg += '\n|{}|{}|'.format(key, badge) - - return msg.format(repo=self.crossbow_repo, branch=self.job.branch) diff --git a/dev/archery/archery/crossbow/tests/fixtures/crossbow-job.yaml b/dev/archery/archery/crossbow/tests/fixtures/crossbow-job.yaml deleted file mode 100644 index c37c7b553a4e..000000000000 --- a/dev/archery/archery/crossbow/tests/fixtures/crossbow-job.yaml +++ /dev/null @@ -1,51 +0,0 @@ -!Job -target: !Target - head: f766a1d615dd1b7ee706d05102e579195951a61c - email: unkown - branch: refs/pull/4435/merge - remote: https://github.com/apache/arrow - version: 0.13.0.dev306 - no_rc_version: 0.13.0.dev306 -tasks: - docker-cpp-cmake32: !Task - ci: circle - platform: linux - template: docker-tests/circle.linux.yml - artifacts: [] - params: - commands: - - docker-compose build cpp-cmake32 - - docker-compose run cpp-cmake32 - branch: ursabot-1-circle-docker-cpp-cmake32 - commit: a56b077c8d1b891a7935048e5672bf6fc07599ec - wheel-osx-cp37m: !Task - ci: travis - platform: osx - template: python-wheels/travis.osx.yml - artifacts: - - pyarrow-0.13.0.dev306-cp37-cp37m-macosx_10_6_intel.whl - params: - python_version: 3.7 - branch: ursabot-1-travis-wheel-osx-cp37m - commit: a56b077c8d1b891a7935048e5672bf6fc07599ec - wheel-osx-cp36m: !Task - ci: travis - platform: osx - template: python-wheels/travis.osx.yml - artifacts: - - pyarrow-0.13.0.dev306-cp36-cp36m-macosx_10_6_intel.whl - params: - python_version: 3.6 - branch: ursabot-1-travis-wheel-osx-cp36m - commit: a56b077c8d1b891a7935048e5672bf6fc07599ec - wheel-win-cp36m: !Task - ci: appveyor - platform: win - template: python-wheels/appveyor.yml - artifacts: - - pyarrow-0.13.0.dev306-cp36-cp36m-win_amd64.whl - params: - python_version: 3.6 - branch: ursabot-1-appveyor-wheel-win-cp36m - commit: a56b077c8d1b891a7935048e5672bf6fc07599ec -branch: ursabot-1 diff --git a/dev/archery/archery/crossbow/tests/fixtures/crossbow-success-message.md b/dev/archery/archery/crossbow/tests/fixtures/crossbow-success-message.md deleted file mode 100644 index f914287dcc09..000000000000 --- a/dev/archery/archery/crossbow/tests/fixtures/crossbow-success-message.md +++ /dev/null @@ -1,10 +0,0 @@ -Revision: {revision} - -Submitted crossbow builds: [{repo} @ {branch}](https://github.com/{repo}/branches/all?query={branch}) - -| Task | Status | -| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| docker-cpp-cmake32 | [![CircleCI](https://img.shields.io/circleci/build/github/{repo}/{branch}-circle-docker-cpp-cmake32.svg)](https://circleci.com/gh/{repo}/tree/{branch}-circle-docker-cpp-cmake32) | -| wheel-osx-cp36m | [![TravisCI](https://img.shields.io/travis/{repo}/{branch}-travis-wheel-osx-cp36m.svg)](https://travis-ci.com/{repo}/branches) | -| wheel-osx-cp37m | [![TravisCI](https://img.shields.io/travis/{repo}/{branch}-travis-wheel-osx-cp37m.svg)](https://travis-ci.com/{repo}/branches) | -| wheel-win-cp36m | [![Appveyor](https://img.shields.io/appveyor/ci/{repo}/{branch}-appveyor-wheel-win-cp36m.svg)](https://ci.appveyor.com/project/{repo}/history) | diff --git a/dev/archery/archery/crossbow/tests/test_core.py b/dev/archery/archery/crossbow/tests/test_core.py deleted file mode 100644 index 518474236aca..000000000000 --- a/dev/archery/archery/crossbow/tests/test_core.py +++ /dev/null @@ -1,25 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -from archery.utils.source import ArrowSources -from archery.crossbow import Config - - -def test_config(): - src = ArrowSources.find() - conf = Config.load_yaml(src.dev / "tasks" / "tasks.yml") - conf.validate() diff --git a/dev/archery/archery/crossbow/tests/test_crossbow_cli.py b/dev/archery/archery/crossbow/tests/test_crossbow_cli.py deleted file mode 100644 index ee9ba1ee2fc8..000000000000 --- a/dev/archery/archery/crossbow/tests/test_crossbow_cli.py +++ /dev/null @@ -1,43 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -from click.testing import CliRunner -import pytest - -from archery.crossbow.cli import crossbow -from archery.utils.git import git - - -@pytest.mark.integration -def test_crossbow_submit(tmp_path): - runner = CliRunner() - - def invoke(*args): - return runner.invoke(crossbow, ['--queue-path', str(tmp_path), *args]) - - # initialize an empty crossbow repository - git.run_cmd("init", str(tmp_path)) - git.run_cmd("-C", str(tmp_path), "remote", "add", "origin", - "https://github.com/dummy/repo") - git.run_cmd("-C", str(tmp_path), "commit", "-m", "initial", - "--allow-empty") - - result = invoke('check-config') - assert result.exit_code == 0 - - result = invoke('submit', '--no-fetch', '--no-push', '-g', 'wheel') - assert result.exit_code == 0 diff --git a/dev/archery/archery/crossbow/tests/test_reports.py b/dev/archery/archery/crossbow/tests/test_reports.py deleted file mode 100644 index 0df292bb557a..000000000000 --- a/dev/archery/archery/crossbow/tests/test_reports.py +++ /dev/null @@ -1,35 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -import textwrap - -from archery.crossbow.core import yaml -from archery.crossbow.reports import CommentReport - - -def test_crossbow_comment_formatter(load_fixture): - msg = load_fixture('crossbow-success-message.md') - job = load_fixture('crossbow-job.yaml', decoder=yaml.load) - - report = CommentReport(job, crossbow_repo='ursa-labs/crossbow') - expected = msg.format( - repo='ursa-labs/crossbow', - branch='ursabot-1', - revision='f766a1d615dd1b7ee706d05102e579195951a61c', - status='has been succeeded.' - ) - assert report.show() == textwrap.dedent(expected).strip() diff --git a/dev/archery/archery/docker.py b/dev/archery/archery/docker.py deleted file mode 100644 index 17d4c713afc9..000000000000 --- a/dev/archery/archery/docker.py +++ /dev/null @@ -1,402 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -import os -import re -import subprocess -from io import StringIO - -from dotenv import dotenv_values -from ruamel.yaml import YAML - -from .utils.command import Command, default_bin -from .compat import _ensure_path - - -def flatten(node, parents=None): - parents = list(parents or []) - if isinstance(node, str): - yield (node, parents) - elif isinstance(node, list): - for value in node: - yield from flatten(value, parents=parents) - elif isinstance(node, dict): - for key, value in node.items(): - yield (key, parents) - yield from flatten(value, parents=parents + [key]) - else: - raise TypeError(node) - - -def _sanitize_command(cmd): - if isinstance(cmd, list): - cmd = " ".join(cmd) - return re.sub(r"\s+", " ", cmd) - - -class UndefinedImage(Exception): - pass - - -class ComposeConfig: - - def __init__(self, config_path, dotenv_path, compose_bin, params=None): - config_path = _ensure_path(config_path) - if dotenv_path: - dotenv_path = _ensure_path(dotenv_path) - else: - dotenv_path = config_path.parent / '.env' - self._read_env(dotenv_path, params) - self._read_config(config_path, compose_bin) - - def _read_env(self, dotenv_path, params): - """ - Read .env and merge it with explicitly passed parameters. - """ - self.dotenv = dotenv_values(str(dotenv_path)) - if params is None: - self.params = {} - else: - self.params = {k: v for k, v in params.items() if k in self.dotenv} - - # forward the process' environment variables - self.env = os.environ.copy() - # set the defaults from the dotenv files - self.env.update(self.dotenv) - # override the defaults passed as parameters - self.env.update(self.params) - - # translate docker's architecture notation to a more widely used one - arch = self.env.get('ARCH', 'amd64') - arch_aliases = { - 'amd64': 'x86_64', - 'arm64v8': 'aarch64', - 's390x': 's390x' - } - arch_short_aliases = { - 'amd64': 'x64', - 'arm64v8': 'arm64', - 's390x': 's390x' - } - self.env['ARCH_ALIAS'] = arch_aliases.get(arch, arch) - self.env['ARCH_SHORT_ALIAS'] = arch_short_aliases.get(arch, arch) - - def _read_config(self, config_path, compose_bin): - """ - Validate and read the docker-compose.yml - """ - yaml = YAML() - with config_path.open() as fp: - config = yaml.load(fp) - - services = config['services'].keys() - self.hierarchy = dict(flatten(config.get('x-hierarchy', {}))) - self.with_gpus = config.get('x-with-gpus', []) - nodes = self.hierarchy.keys() - errors = [] - - for name in self.with_gpus: - if name not in services: - errors.append( - 'Service `{}` defined in `x-with-gpus` bot not in ' - '`services`'.format(name) - ) - for name in nodes - services: - errors.append( - 'Service `{}` is defined in `x-hierarchy` bot not in ' - '`services`'.format(name) - ) - for name in services - nodes: - errors.append( - 'Service `{}` is defined in `services` but not in ' - '`x-hierarchy`'.format(name) - ) - - # trigger docker-compose's own validation - compose = Command('docker-compose') - args = ['--file', str(config_path), 'config'] - result = compose.run(*args, env=self.env, check=False, - stderr=subprocess.PIPE, stdout=subprocess.PIPE) - - if result.returncode != 0: - # strip the intro line of docker-compose errors - errors += result.stderr.decode().splitlines() - - if errors: - msg = '\n'.join([' - {}'.format(msg) for msg in errors]) - raise ValueError( - 'Found errors with docker-compose:\n{}'.format(msg) - ) - - rendered_config = StringIO(result.stdout.decode()) - self.path = config_path - self.config = yaml.load(rendered_config) - - def get(self, service_name): - try: - service = self.config['services'][service_name] - except KeyError: - raise UndefinedImage(service_name) - service['name'] = service_name - service['need_gpu'] = service_name in self.with_gpus - service['ancestors'] = self.hierarchy[service_name] - return service - - def __getitem__(self, service_name): - return self.get(service_name) - - -class Docker(Command): - - def __init__(self, docker_bin=None): - self.bin = default_bin(docker_bin, "docker") - - -class DockerCompose(Command): - - def __init__(self, config_path, dotenv_path=None, compose_bin=None, - params=None): - compose_bin = default_bin(compose_bin, 'docker-compose') - self.config = ComposeConfig(config_path, dotenv_path, compose_bin, - params) - self.bin = compose_bin - self.pull_memory = set() - - def clear_pull_memory(self): - self.pull_memory = set() - - def _execute_compose(self, *args, **kwargs): - # execute as a docker compose command - try: - result = super().run('--file', str(self.config.path), *args, - env=self.config.env, **kwargs) - result.check_returncode() - except subprocess.CalledProcessError as e: - def formatdict(d, template): - return '\n'.join( - template.format(k, v) for k, v in sorted(d.items()) - ) - msg = ( - "`{cmd}` exited with a non-zero exit code {code}, see the " - "process log above.\n\nThe docker-compose command was " - "invoked with the following parameters:\n\nDefaults defined " - "in .env:\n{dotenv}\n\nArchery was called with:\n{params}" - ) - raise RuntimeError( - msg.format( - cmd=' '.join(e.cmd), - code=e.returncode, - dotenv=formatdict(self.config.dotenv, template=' {}: {}'), - params=formatdict( - self.config.params, template=' export {}={}' - ) - ) - ) - - def _execute_docker(self, *args, **kwargs): - # execute as a plain docker cli command - try: - result = Docker().run(*args, **kwargs) - result.check_returncode() - except subprocess.CalledProcessError as e: - raise RuntimeError( - "{} exited with non-zero exit code {}".format( - ' '.join(e.cmd), e.returncode - ) - ) - - def pull(self, service_name, pull_leaf=True, using_docker=False): - def _pull(service): - args = ['pull'] - if service['image'] in self.pull_memory: - return - - if using_docker: - try: - self._execute_docker(*args, service['image']) - except Exception as e: - # better --ignore-pull-failures handling - print(e) - else: - args.append('--ignore-pull-failures') - self._execute_compose(*args, service['name']) - - self.pull_memory.add(service['image']) - - service = self.config.get(service_name) - for ancestor in service['ancestors']: - _pull(self.config.get(ancestor)) - if pull_leaf: - _pull(service) - - def build(self, service_name, use_cache=True, use_leaf_cache=True, - using_docker=False, using_buildx=False): - def _build(service, use_cache): - if 'build' not in service: - # nothing to do - return - - args = [] - cache_from = list(service.get('build', {}).get('cache_from', [])) - if use_cache: - for image in cache_from: - if image not in self.pull_memory: - try: - self._execute_docker('pull', image) - except Exception as e: - print(e) - finally: - self.pull_memory.add(image) - else: - args.append('--no-cache') - - # turn on inline build cache, this is a docker buildx feature - # used to bundle the image build cache to the pushed image manifest - # so the build cache can be reused across hosts, documented at - # https://github.com/docker/buildx#--cache-tonametypetypekeyvalue - if self.config.env.get('BUILDKIT_INLINE_CACHE') == '1': - args.extend(['--build-arg', 'BUILDKIT_INLINE_CACHE=1']) - - if using_buildx: - for k, v in service['build'].get('args', {}).items(): - args.extend(['--build-arg', '{}={}'.format(k, v)]) - - if use_cache: - cache_ref = '{}-cache'.format(service['image']) - cache_from = 'type=registry,ref={}'.format(cache_ref) - cache_to = ( - 'type=registry,ref={},mode=max'.format(cache_ref) - ) - args.extend([ - '--cache-from', cache_from, - '--cache-to', cache_to, - ]) - - args.extend([ - '--output', 'type=docker', - '-f', service['build']['dockerfile'], - '-t', service['image'], - service['build'].get('context', '.') - ]) - self._execute_docker("buildx", "build", *args) - elif using_docker: - # better for caching - for k, v in service['build'].get('args', {}).items(): - args.extend(['--build-arg', '{}={}'.format(k, v)]) - for img in cache_from: - args.append('--cache-from="{}"'.format(img)) - args.extend([ - '-f', service['build']['dockerfile'], - '-t', service['image'], - service['build'].get('context', '.') - ]) - self._execute_docker("build", *args) - else: - self._execute_compose("build", *args, service['name']) - - service = self.config.get(service_name) - # build ancestor services - for ancestor in service['ancestors']: - _build(self.config.get(ancestor), use_cache=use_cache) - # build the leaf/target service - _build(service, use_cache=use_cache and use_leaf_cache) - - def run(self, service_name, command=None, *, env=None, volumes=None, - user=None, using_docker=False): - service = self.config.get(service_name) - - args = [] - if user is not None: - args.extend(['-u', user]) - - if env is not None: - for k, v in env.items(): - args.extend(['-e', '{}={}'.format(k, v)]) - - if volumes is not None: - for volume in volumes: - args.extend(['--volume', volume]) - - if using_docker or service['need_gpu']: - # use gpus, requires docker>=19.03 - if service['need_gpu']: - args.extend(['--gpus', 'all']) - - if service.get('shm_size'): - args.extend(['--shm-size', service['shm_size']]) - - # append env variables from the compose conf - for k, v in service.get('environment', {}).items(): - args.extend(['-e', '{}={}'.format(k, v)]) - - # append volumes from the compose conf - for v in service.get('volumes', []): - if not isinstance(v, str): - # if not the compact string volume definition - v = "{}:{}".format(v['source'], v['target']) - args.extend(['-v', v]) - - # infer whether an interactive shell is desired or not - if command in ['cmd.exe', 'bash', 'sh', 'powershell']: - args.append('-it') - - # get the actual docker image name instead of the compose service - # name which we refer as image in general - args.append(service['image']) - - # add command from compose if it wasn't overridden - if command is not None: - args.append(command) - else: - # replace whitespaces from the preformatted compose command - cmd = _sanitize_command(service.get('command', '')) - if cmd: - args.append(cmd) - - # execute as a plain docker cli command - self._execute_docker('run', '--rm', *args) - else: - # execute as a docker-compose command - args.append(service_name) - if command is not None: - args.append(command) - self._execute_compose('run', '--rm', *args) - - def push(self, service_name, user=None, password=None, using_docker=False): - def _push(service): - if using_docker: - return self._execute_docker('push', service['image']) - else: - return self._execute_compose('push', service['name']) - - if user is not None: - try: - # TODO(kszucs): have an option for a prompt - self._execute_docker('login', '-u', user, '-p', password) - except subprocess.CalledProcessError: - # hide credentials - msg = ('Failed to push `{}`, check the passed credentials' - .format(service_name)) - raise RuntimeError(msg) from None - - service = self.config.get(service_name) - for ancestor in service['ancestors']: - _push(self.config.get(ancestor)) - _push(service) - - def images(self): - return sorted(self.config.hierarchy.keys()) diff --git a/dev/archery/archery/integration/__init__.py b/dev/archery/archery/integration/__init__.py deleted file mode 100644 index 13a83393a912..000000000000 --- a/dev/archery/archery/integration/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. diff --git a/dev/archery/archery/integration/datagen.py b/dev/archery/archery/integration/datagen.py deleted file mode 100644 index 35ab289cc33d..000000000000 --- a/dev/archery/archery/integration/datagen.py +++ /dev/null @@ -1,1604 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -from collections import namedtuple, OrderedDict -import binascii -import json -import os -import random -import tempfile - -import numpy as np - -from .util import frombytes, tobytes, random_bytes, random_utf8 - - -def metadata_key_values(pairs): - return [{'key': k, 'value': v} for k, v in pairs] - - -class Field(object): - - def __init__(self, name, *, nullable=True, metadata=None): - self.name = name - self.nullable = nullable - self.metadata = metadata or [] - - def get_json(self): - entries = [ - ('name', self.name), - ('type', self._get_type()), - ('nullable', self.nullable), - ('children', self._get_children()), - ] - - dct = self._get_dictionary() - if dct: - entries.append(('dictionary', dct)) - - if self.metadata is not None and len(self.metadata) > 0: - entries.append(('metadata', metadata_key_values(self.metadata))) - - return OrderedDict(entries) - - def _get_dictionary(self): - return None - - def _make_is_valid(self, size, null_probability=0.4): - if self.nullable: - return (np.random.random_sample(size) > null_probability - ).astype(np.int8) - else: - return np.ones(size, dtype=np.int8) - - -class Column(object): - - def __init__(self, name, count): - self.name = name - self.count = count - - def __len__(self): - return self.count - - def _get_children(self): - return [] - - def _get_buffers(self): - return [] - - def get_json(self): - entries = [ - ('name', self.name), - ('count', self.count) - ] - - buffers = self._get_buffers() - entries.extend(buffers) - - children = self._get_children() - if len(children) > 0: - entries.append(('children', children)) - - return OrderedDict(entries) - - -class PrimitiveField(Field): - - def _get_children(self): - return [] - - -class PrimitiveColumn(Column): - - def __init__(self, name, count, is_valid, values): - super().__init__(name, count) - self.is_valid = is_valid - self.values = values - - def _encode_value(self, x): - return x - - def _get_buffers(self): - return [ - ('VALIDITY', [int(v) for v in self.is_valid]), - ('DATA', list([self._encode_value(x) for x in self.values])) - ] - - -class NullColumn(Column): - # This subclass is for readability only - pass - - -class NullField(PrimitiveField): - - def __init__(self, name, metadata=None): - super().__init__(name, nullable=True, - metadata=metadata) - - def _get_type(self): - return OrderedDict([('name', 'null')]) - - def generate_column(self, size, name=None): - return NullColumn(name or self.name, size) - - -TEST_INT_MAX = 2 ** 31 - 1 -TEST_INT_MIN = ~TEST_INT_MAX - - -class IntegerField(PrimitiveField): - - def __init__(self, name, is_signed, bit_width, *, nullable=True, - metadata=None, - min_value=TEST_INT_MIN, - max_value=TEST_INT_MAX): - super().__init__(name, nullable=nullable, - metadata=metadata) - self.is_signed = is_signed - self.bit_width = bit_width - self.min_value = min_value - self.max_value = max_value - - def _get_generated_data_bounds(self): - if self.is_signed: - signed_iinfo = np.iinfo('int' + str(self.bit_width)) - min_value, max_value = signed_iinfo.min, signed_iinfo.max - else: - unsigned_iinfo = np.iinfo('uint' + str(self.bit_width)) - min_value, max_value = 0, unsigned_iinfo.max - - lower_bound = max(min_value, self.min_value) - upper_bound = min(max_value, self.max_value) - return lower_bound, upper_bound - - def _get_type(self): - return OrderedDict([ - ('name', 'int'), - ('isSigned', self.is_signed), - ('bitWidth', self.bit_width) - ]) - - def generate_column(self, size, name=None): - lower_bound, upper_bound = self._get_generated_data_bounds() - return self.generate_range(size, lower_bound, upper_bound, - name=name, include_extremes=True) - - def generate_range(self, size, lower, upper, name=None, - include_extremes=False): - values = np.random.randint(lower, upper, size=size, dtype=np.int64) - if include_extremes and size >= 2: - values[:2] = [lower, upper] - values = list(map(int if self.bit_width < 64 else str, values)) - - is_valid = self._make_is_valid(size) - - if name is None: - name = self.name - return PrimitiveColumn(name, size, is_valid, values) - - -class DateField(IntegerField): - - DAY = 0 - MILLISECOND = 1 - - # 1/1/1 to 12/31/9999 - _ranges = { - DAY: [-719162, 2932896], - MILLISECOND: [-62135596800000, 253402214400000] - } - - def __init__(self, name, unit, *, nullable=True, metadata=None): - bit_width = 32 if unit == self.DAY else 64 - - min_value, max_value = self._ranges[unit] - super().__init__( - name, True, bit_width, - nullable=nullable, metadata=metadata, - min_value=min_value, max_value=max_value - ) - self.unit = unit - - def _get_type(self): - return OrderedDict([ - ('name', 'date'), - ('unit', 'DAY' if self.unit == self.DAY else 'MILLISECOND') - ]) - - -TIMEUNIT_NAMES = { - 's': 'SECOND', - 'ms': 'MILLISECOND', - 'us': 'MICROSECOND', - 'ns': 'NANOSECOND' -} - - -class TimeField(IntegerField): - - BIT_WIDTHS = { - 's': 32, - 'ms': 32, - 'us': 64, - 'ns': 64 - } - - _ranges = { - 's': [0, 86400], - 'ms': [0, 86400000], - 'us': [0, 86400000000], - 'ns': [0, 86400000000000] - } - - def __init__(self, name, unit='s', *, nullable=True, - metadata=None): - min_val, max_val = self._ranges[unit] - super().__init__(name, True, self.BIT_WIDTHS[unit], - nullable=nullable, metadata=metadata, - min_value=min_val, max_value=max_val) - self.unit = unit - - def _get_type(self): - return OrderedDict([ - ('name', 'time'), - ('unit', TIMEUNIT_NAMES[self.unit]), - ('bitWidth', self.bit_width) - ]) - - -class TimestampField(IntegerField): - - # 1/1/1 to 12/31/9999 - _ranges = { - 's': [-62135596800, 253402214400], - 'ms': [-62135596800000, 253402214400000], - 'us': [-62135596800000000, 253402214400000000], - - # Physical range for int64, ~584 years and change - 'ns': [np.iinfo('int64').min, np.iinfo('int64').max] - } - - def __init__(self, name, unit='s', tz=None, *, nullable=True, - metadata=None): - min_val, max_val = self._ranges[unit] - super().__init__(name, True, 64, - nullable=nullable, - metadata=metadata, - min_value=min_val, - max_value=max_val) - self.unit = unit - self.tz = tz - - def _get_type(self): - fields = [ - ('name', 'timestamp'), - ('unit', TIMEUNIT_NAMES[self.unit]) - ] - - if self.tz is not None: - fields.append(('timezone', self.tz)) - - return OrderedDict(fields) - - -class DurationIntervalField(IntegerField): - - def __init__(self, name, unit='s', *, nullable=True, - metadata=None): - min_val, max_val = np.iinfo('int64').min, np.iinfo('int64').max, - super().__init__( - name, True, 64, - nullable=nullable, metadata=metadata, - min_value=min_val, max_value=max_val) - self.unit = unit - - def _get_type(self): - fields = [ - ('name', 'duration'), - ('unit', TIMEUNIT_NAMES[self.unit]) - ] - - return OrderedDict(fields) - - -class YearMonthIntervalField(IntegerField): - def __init__(self, name, *, nullable=True, metadata=None): - min_val, max_val = [-10000*12, 10000*12] # +/- 10000 years. - super().__init__( - name, True, 32, - nullable=nullable, metadata=metadata, - min_value=min_val, max_value=max_val) - - def _get_type(self): - fields = [ - ('name', 'interval'), - ('unit', 'YEAR_MONTH'), - ] - - return OrderedDict(fields) - - -class DayTimeIntervalField(PrimitiveField): - def __init__(self, name, *, nullable=True, metadata=None): - super().__init__(name, - nullable=True, - metadata=metadata) - - @property - def numpy_type(self): - return object - - def _get_type(self): - - return OrderedDict([ - ('name', 'interval'), - ('unit', 'DAY_TIME'), - ]) - - def generate_column(self, size, name=None): - min_day_value, max_day_value = -10000*366, 10000*366 - values = [{'days': random.randint(min_day_value, max_day_value), - 'milliseconds': random.randint(-86400000, +86400000)} - for _ in range(size)] - - is_valid = self._make_is_valid(size) - if name is None: - name = self.name - return PrimitiveColumn(name, size, is_valid, values) - - -class FloatingPointField(PrimitiveField): - - def __init__(self, name, bit_width, *, nullable=True, - metadata=None): - super().__init__(name, - nullable=nullable, - metadata=metadata) - - self.bit_width = bit_width - self.precision = { - 16: 'HALF', - 32: 'SINGLE', - 64: 'DOUBLE' - }[self.bit_width] - - @property - def numpy_type(self): - return 'float' + str(self.bit_width) - - def _get_type(self): - return OrderedDict([ - ('name', 'floatingpoint'), - ('precision', self.precision) - ]) - - def generate_column(self, size, name=None): - values = np.random.randn(size) * 1000 - values = np.round(values, 3) - - is_valid = self._make_is_valid(size) - if name is None: - name = self.name - return PrimitiveColumn(name, size, is_valid, values) - - -DECIMAL_PRECISION_TO_VALUE = { - key: (1 << (8 * i - 1)) - 1 for i, key in enumerate( - [1, 3, 5, 7, 10, 12, 15, 17, 19, 22, 24, 27, 29, 32, 34, 36, - 40, 42, 44, 50, 60, 70], - start=1, - ) -} - - -def decimal_range_from_precision(precision): - assert 1 <= precision <= 76 - try: - max_value = DECIMAL_PRECISION_TO_VALUE[precision] - except KeyError: - return decimal_range_from_precision(precision - 1) - else: - return ~max_value, max_value - - -class DecimalField(PrimitiveField): - def __init__(self, name, precision, scale, bit_width, *, - nullable=True, metadata=None): - super().__init__(name, nullable=True, - metadata=metadata) - self.precision = precision - self.scale = scale - self.bit_width = bit_width - - @property - def numpy_type(self): - return object - - def _get_type(self): - return OrderedDict([ - ('name', 'decimal'), - ('precision', self.precision), - ('scale', self.scale), - ('bitWidth', self.bit_width), - ]) - - def generate_column(self, size, name=None): - min_value, max_value = decimal_range_from_precision(self.precision) - values = [random.randint(min_value, max_value) for _ in range(size)] - - is_valid = self._make_is_valid(size) - if name is None: - name = self.name - return DecimalColumn(name, size, is_valid, values, self.bit_width) - - -class DecimalColumn(PrimitiveColumn): - - def __init__(self, name, count, is_valid, values, bit_width): - super().__init__(name, count, is_valid, values) - self.bit_width = bit_width - - def _encode_value(self, x): - return str(x) - - -class BooleanField(PrimitiveField): - bit_width = 1 - - def _get_type(self): - return OrderedDict([('name', 'bool')]) - - @property - def numpy_type(self): - return 'bool' - - def generate_column(self, size, name=None): - values = list(map(bool, np.random.randint(0, 2, size=size))) - is_valid = self._make_is_valid(size) - if name is None: - name = self.name - return PrimitiveColumn(name, size, is_valid, values) - - -class FixedSizeBinaryField(PrimitiveField): - - def __init__(self, name, byte_width, *, nullable=True, - metadata=None): - super().__init__(name, nullable=nullable, - metadata=metadata) - self.byte_width = byte_width - - @property - def numpy_type(self): - return object - - @property - def column_class(self): - return FixedSizeBinaryColumn - - def _get_type(self): - return OrderedDict([('name', 'fixedsizebinary'), - ('byteWidth', self.byte_width)]) - - def generate_column(self, size, name=None): - is_valid = self._make_is_valid(size) - values = [] - - for i in range(size): - values.append(random_bytes(self.byte_width)) - - if name is None: - name = self.name - return self.column_class(name, size, is_valid, values) - - -class BinaryField(PrimitiveField): - - @property - def numpy_type(self): - return object - - @property - def column_class(self): - return BinaryColumn - - def _get_type(self): - return OrderedDict([('name', 'binary')]) - - def _random_sizes(self, size): - return np.random.exponential(scale=4, size=size).astype(np.int32) - - def generate_column(self, size, name=None): - is_valid = self._make_is_valid(size) - values = [] - - sizes = self._random_sizes(size) - - for i, nbytes in enumerate(sizes): - if is_valid[i]: - values.append(random_bytes(nbytes)) - else: - values.append(b"") - - if name is None: - name = self.name - return self.column_class(name, size, is_valid, values) - - -class StringField(BinaryField): - - @property - def column_class(self): - return StringColumn - - def _get_type(self): - return OrderedDict([('name', 'utf8')]) - - def generate_column(self, size, name=None): - K = 7 - is_valid = self._make_is_valid(size) - values = [] - - for i in range(size): - if is_valid[i]: - values.append(tobytes(random_utf8(K))) - else: - values.append(b"") - - if name is None: - name = self.name - return self.column_class(name, size, is_valid, values) - - -class LargeBinaryField(BinaryField): - - @property - def column_class(self): - return LargeBinaryColumn - - def _get_type(self): - return OrderedDict([('name', 'largebinary')]) - - -class LargeStringField(StringField): - - @property - def column_class(self): - return LargeStringColumn - - def _get_type(self): - return OrderedDict([('name', 'largeutf8')]) - - -class Schema(object): - - def __init__(self, fields, metadata=None): - self.fields = fields - self.metadata = metadata - - def get_json(self): - entries = [ - ('fields', [field.get_json() for field in self.fields]) - ] - - if self.metadata is not None and len(self.metadata) > 0: - entries.append(('metadata', metadata_key_values(self.metadata))) - - return OrderedDict(entries) - - -class _NarrowOffsetsMixin: - - def _encode_offsets(self, offsets): - return list(map(int, offsets)) - - -class _LargeOffsetsMixin: - - def _encode_offsets(self, offsets): - # 64-bit offsets have to be represented as strings to roundtrip - # through JSON. - return list(map(str, offsets)) - - -class _BaseBinaryColumn(PrimitiveColumn): - - def _encode_value(self, x): - return frombytes(binascii.hexlify(x).upper()) - - def _get_buffers(self): - offset = 0 - offsets = [0] - - data = [] - for i, v in enumerate(self.values): - if self.is_valid[i]: - offset += len(v) - else: - v = b"" - - offsets.append(offset) - data.append(self._encode_value(v)) - - return [ - ('VALIDITY', [int(x) for x in self.is_valid]), - ('OFFSET', self._encode_offsets(offsets)), - ('DATA', data) - ] - - -class _BaseStringColumn(_BaseBinaryColumn): - - def _encode_value(self, x): - return frombytes(x) - - -class BinaryColumn(_BaseBinaryColumn, _NarrowOffsetsMixin): - pass - - -class StringColumn(_BaseStringColumn, _NarrowOffsetsMixin): - pass - - -class LargeBinaryColumn(_BaseBinaryColumn, _LargeOffsetsMixin): - pass - - -class LargeStringColumn(_BaseStringColumn, _LargeOffsetsMixin): - pass - - -class FixedSizeBinaryColumn(PrimitiveColumn): - - def _encode_value(self, x): - return frombytes(binascii.hexlify(x).upper()) - - def _get_buffers(self): - data = [] - for i, v in enumerate(self.values): - data.append(self._encode_value(v)) - - return [ - ('VALIDITY', [int(x) for x in self.is_valid]), - ('DATA', data) - ] - - -class ListField(Field): - - def __init__(self, name, value_field, *, nullable=True, - metadata=None): - super().__init__(name, nullable=nullable, - metadata=metadata) - self.value_field = value_field - - @property - def column_class(self): - return ListColumn - - def _get_type(self): - return OrderedDict([ - ('name', 'list') - ]) - - def _get_children(self): - return [self.value_field.get_json()] - - def generate_column(self, size, name=None): - MAX_LIST_SIZE = 4 - - is_valid = self._make_is_valid(size) - list_sizes = np.random.randint(0, MAX_LIST_SIZE + 1, size=size) - offsets = [0] - - offset = 0 - for i in range(size): - if is_valid[i]: - offset += int(list_sizes[i]) - offsets.append(offset) - - # The offset now is the total number of elements in the child array - values = self.value_field.generate_column(offset) - - if name is None: - name = self.name - return self.column_class(name, size, is_valid, offsets, values) - - -class LargeListField(ListField): - - @property - def column_class(self): - return LargeListColumn - - def _get_type(self): - return OrderedDict([ - ('name', 'largelist') - ]) - - -class _BaseListColumn(Column): - - def __init__(self, name, count, is_valid, offsets, values): - super().__init__(name, count) - self.is_valid = is_valid - self.offsets = offsets - self.values = values - - def _get_buffers(self): - return [ - ('VALIDITY', [int(v) for v in self.is_valid]), - ('OFFSET', self._encode_offsets(self.offsets)) - ] - - def _get_children(self): - return [self.values.get_json()] - - -class ListColumn(_BaseListColumn, _NarrowOffsetsMixin): - pass - - -class LargeListColumn(_BaseListColumn, _LargeOffsetsMixin): - pass - - -class MapField(Field): - - def __init__(self, name, key_field, item_field, *, nullable=True, - metadata=None, keys_sorted=False, entries_name='entries'): - super().__init__(name, nullable=nullable, - metadata=metadata) - - assert not key_field.nullable - self.key_field = key_field - self.item_field = item_field - self.pair_field = StructField(entries_name, [key_field, item_field], - nullable=False) - self.keys_sorted = keys_sorted - - def _get_type(self): - return OrderedDict([ - ('name', 'map'), - ('keysSorted', self.keys_sorted) - ]) - - def _get_children(self): - return [self.pair_field.get_json()] - - def generate_column(self, size, name=None): - MAX_MAP_SIZE = 4 - - is_valid = self._make_is_valid(size) - map_sizes = np.random.randint(0, MAX_MAP_SIZE + 1, size=size) - offsets = [0] - - offset = 0 - for i in range(size): - if is_valid[i]: - offset += int(map_sizes[i]) - offsets.append(offset) - - # The offset now is the total number of elements in the child array - pairs = self.pair_field.generate_column(offset) - if name is None: - name = self.name - - return MapColumn(name, size, is_valid, offsets, pairs) - - -class MapColumn(Column): - - def __init__(self, name, count, is_valid, offsets, pairs): - super().__init__(name, count) - self.is_valid = is_valid - self.offsets = offsets - self.pairs = pairs - - def _get_buffers(self): - return [ - ('VALIDITY', [int(v) for v in self.is_valid]), - ('OFFSET', list(self.offsets)) - ] - - def _get_children(self): - return [self.pairs.get_json()] - - -class FixedSizeListField(Field): - - def __init__(self, name, value_field, list_size, *, nullable=True, - metadata=None): - super().__init__(name, nullable=nullable, - metadata=metadata) - self.value_field = value_field - self.list_size = list_size - - def _get_type(self): - return OrderedDict([ - ('name', 'fixedsizelist'), - ('listSize', self.list_size) - ]) - - def _get_children(self): - return [self.value_field.get_json()] - - def generate_column(self, size, name=None): - is_valid = self._make_is_valid(size) - values = self.value_field.generate_column(size * self.list_size) - - if name is None: - name = self.name - return FixedSizeListColumn(name, size, is_valid, values) - - -class FixedSizeListColumn(Column): - - def __init__(self, name, count, is_valid, values): - super().__init__(name, count) - self.is_valid = is_valid - self.values = values - - def _get_buffers(self): - return [ - ('VALIDITY', [int(v) for v in self.is_valid]) - ] - - def _get_children(self): - return [self.values.get_json()] - - -class StructField(Field): - - def __init__(self, name, fields, *, nullable=True, - metadata=None): - super().__init__(name, nullable=nullable, - metadata=metadata) - self.fields = fields - - def _get_type(self): - return OrderedDict([ - ('name', 'struct') - ]) - - def _get_children(self): - return [field.get_json() for field in self.fields] - - def generate_column(self, size, name=None): - is_valid = self._make_is_valid(size) - - field_values = [field.generate_column(size) for field in self.fields] - if name is None: - name = self.name - return StructColumn(name, size, is_valid, field_values) - - -class _BaseUnionField(Field): - - def __init__(self, name, fields, type_ids=None, *, nullable=True, - metadata=None): - super().__init__(name, nullable=nullable, metadata=metadata) - if type_ids is None: - type_ids = list(range(fields)) - else: - assert len(fields) == len(type_ids) - self.fields = fields - self.type_ids = type_ids - assert all(x >= 0 for x in self.type_ids) - - def _get_type(self): - return OrderedDict([ - ('name', 'union'), - ('mode', self.mode), - ('typeIds', self.type_ids), - ]) - - def _get_children(self): - return [field.get_json() for field in self.fields] - - def _make_type_ids(self, size): - return np.random.choice(self.type_ids, size) - - -class SparseUnionField(_BaseUnionField): - mode = 'SPARSE' - - def generate_column(self, size, name=None): - array_type_ids = self._make_type_ids(size) - field_values = [field.generate_column(size) for field in self.fields] - - if name is None: - name = self.name - return SparseUnionColumn(name, size, array_type_ids, field_values) - - -class DenseUnionField(_BaseUnionField): - mode = 'DENSE' - - def generate_column(self, size, name=None): - # Reverse mapping {logical type id => physical child id} - child_ids = [None] * (max(self.type_ids) + 1) - for i, type_id in enumerate(self.type_ids): - child_ids[type_id] = i - - array_type_ids = self._make_type_ids(size) - offsets = [] - child_sizes = [0] * len(self.fields) - - for i in range(size): - child_id = child_ids[array_type_ids[i]] - offset = child_sizes[child_id] - offsets.append(offset) - child_sizes[child_id] = offset + 1 - - field_values = [ - field.generate_column(child_size) - for field, child_size in zip(self.fields, child_sizes)] - - if name is None: - name = self.name - return DenseUnionColumn(name, size, array_type_ids, offsets, - field_values) - - -class Dictionary(object): - - def __init__(self, id_, field, size, name=None, ordered=False): - self.id_ = id_ - self.field = field - self.values = field.generate_column(size=size, name=name) - self.ordered = ordered - - def __len__(self): - return len(self.values) - - def get_json(self): - dummy_batch = RecordBatch(len(self.values), [self.values]) - return OrderedDict([ - ('id', self.id_), - ('data', dummy_batch.get_json()) - ]) - - -class DictionaryField(Field): - - def __init__(self, name, index_field, dictionary, *, nullable=True, - metadata=None): - super().__init__(name, nullable=nullable, - metadata=metadata) - assert index_field.name == '' - assert isinstance(index_field, IntegerField) - assert isinstance(dictionary, Dictionary) - - self.index_field = index_field - self.dictionary = dictionary - - def _get_type(self): - return self.dictionary.field._get_type() - - def _get_children(self): - return self.dictionary.field._get_children() - - def _get_dictionary(self): - return OrderedDict([ - ('id', self.dictionary.id_), - ('indexType', self.index_field._get_type()), - ('isOrdered', self.dictionary.ordered) - ]) - - def generate_column(self, size, name=None): - if name is None: - name = self.name - return self.index_field.generate_range(size, 0, len(self.dictionary), - name=name) - - -ExtensionType = namedtuple( - 'ExtensionType', ['extension_name', 'serialized', 'storage_field']) - - -class ExtensionField(Field): - - def __init__(self, name, extension_type, *, nullable=True, metadata=None): - metadata = (metadata or []) + [ - ('ARROW:extension:name', extension_type.extension_name), - ('ARROW:extension:metadata', extension_type.serialized), - ] - super().__init__(name, nullable=nullable, metadata=metadata) - self.extension_type = extension_type - - def _get_type(self): - return self.extension_type.storage_field._get_type() - - def _get_children(self): - return self.extension_type.storage_field._get_children() - - def _get_dictionary(self): - return self.extension_type.storage_field._get_dictionary() - - def generate_column(self, size, name=None): - if name is None: - name = self.name - return self.extension_type.storage_field.generate_column(size, name) - - -class StructColumn(Column): - - def __init__(self, name, count, is_valid, field_values): - super().__init__(name, count) - self.is_valid = is_valid - self.field_values = field_values - - def _get_buffers(self): - return [ - ('VALIDITY', [int(v) for v in self.is_valid]) - ] - - def _get_children(self): - return [field.get_json() for field in self.field_values] - - -class SparseUnionColumn(Column): - - def __init__(self, name, count, type_ids, field_values): - super().__init__(name, count) - self.type_ids = type_ids - self.field_values = field_values - - def _get_buffers(self): - return [ - ('TYPE_ID', [int(v) for v in self.type_ids]) - ] - - def _get_children(self): - return [field.get_json() for field in self.field_values] - - -class DenseUnionColumn(Column): - - def __init__(self, name, count, type_ids, offsets, field_values): - super().__init__(name, count) - self.type_ids = type_ids - self.offsets = offsets - self.field_values = field_values - - def _get_buffers(self): - return [ - ('TYPE_ID', [int(v) for v in self.type_ids]), - ('OFFSET', [int(v) for v in self.offsets]), - ] - - def _get_children(self): - return [field.get_json() for field in self.field_values] - - -class RecordBatch(object): - - def __init__(self, count, columns): - self.count = count - self.columns = columns - - def get_json(self): - return OrderedDict([ - ('count', self.count), - ('columns', [col.get_json() for col in self.columns]) - ]) - - -class File(object): - - def __init__(self, name, schema, batches, dictionaries=None, - skip=None, path=None): - self.name = name - self.schema = schema - self.dictionaries = dictionaries or [] - self.batches = batches - self.skip = set() - self.path = path - if skip: - self.skip.update(skip) - - def get_json(self): - entries = [ - ('schema', self.schema.get_json()) - ] - - if len(self.dictionaries) > 0: - entries.append(('dictionaries', - [dictionary.get_json() - for dictionary in self.dictionaries])) - - entries.append(('batches', [batch.get_json() - for batch in self.batches])) - return OrderedDict(entries) - - def write(self, path): - with open(path, 'wb') as f: - f.write(json.dumps(self.get_json(), indent=2).encode('utf-8')) - self.path = path - - def skip_category(self, category): - """Skip this test for the given category. - - Category should be SKIP_ARROW or SKIP_FLIGHT. - """ - self.skip.add(category) - return self - - -def get_field(name, type_, **kwargs): - if type_ == 'binary': - return BinaryField(name, **kwargs) - elif type_ == 'utf8': - return StringField(name, **kwargs) - elif type_ == 'largebinary': - return LargeBinaryField(name, **kwargs) - elif type_ == 'largeutf8': - return LargeStringField(name, **kwargs) - elif type_.startswith('fixedsizebinary_'): - byte_width = int(type_.split('_')[1]) - return FixedSizeBinaryField(name, byte_width=byte_width, **kwargs) - - dtype = np.dtype(type_) - - if dtype.kind in ('i', 'u'): - signed = dtype.kind == 'i' - bit_width = dtype.itemsize * 8 - return IntegerField(name, signed, bit_width, **kwargs) - elif dtype.kind == 'f': - bit_width = dtype.itemsize * 8 - return FloatingPointField(name, bit_width, **kwargs) - elif dtype.kind == 'b': - return BooleanField(name, **kwargs) - else: - raise TypeError(dtype) - - -def _generate_file(name, fields, batch_sizes, dictionaries=None, skip=None, - metadata=None): - schema = Schema(fields, metadata=metadata) - batches = [] - for size in batch_sizes: - columns = [] - for field in fields: - col = field.generate_column(size) - columns.append(col) - - batches.append(RecordBatch(size, columns)) - - return File(name, schema, batches, dictionaries, skip=skip) - - -def generate_custom_metadata_case(): - def meta(items): - # Generate a simple block of metadata where each value is '{}'. - # Keys are delimited by whitespace in `items`. - return [(k, '{}') for k in items.split()] - - fields = [ - get_field('sort_of_pandas', 'int8', metadata=meta('pandas')), - - get_field('lots_of_meta', 'int8', metadata=meta('a b c d .. w x y z')), - - get_field( - 'unregistered_extension', 'int8', - metadata=[ - ('ARROW:extension:name', '!nonexistent'), - ('ARROW:extension:metadata', ''), - ('ARROW:integration:allow_unregistered_extension', 'true'), - ]), - - ListField('list_with_odd_values', - get_field('item', 'int32', metadata=meta('odd_values'))), - ] - - batch_sizes = [1] - return _generate_file('custom_metadata', fields, batch_sizes, - metadata=meta('schema_custom_0 schema_custom_1')) - - -def generate_duplicate_fieldnames_case(): - fields = [ - get_field('ints', 'int8'), - get_field('ints', 'int32'), - - StructField('struct', [get_field('', 'int32'), get_field('', 'utf8')]), - ] - - batch_sizes = [1] - return _generate_file('duplicate_fieldnames', fields, batch_sizes) - - -def generate_primitive_case(batch_sizes, name='primitive'): - types = ['bool', 'int8', 'int16', 'int32', 'int64', - 'uint8', 'uint16', 'uint32', 'uint64', - 'float32', 'float64', 'binary', 'utf8', - 'fixedsizebinary_19', 'fixedsizebinary_120'] - - fields = [] - - for type_ in types: - fields.append(get_field(type_ + "_nullable", type_, nullable=True)) - fields.append(get_field(type_ + "_nonnullable", type_, nullable=False)) - - return _generate_file(name, fields, batch_sizes) - - -def generate_primitive_large_offsets_case(batch_sizes): - types = ['largebinary', 'largeutf8'] - - fields = [] - - for type_ in types: - fields.append(get_field(type_ + "_nullable", type_, nullable=True)) - fields.append(get_field(type_ + "_nonnullable", type_, nullable=False)) - - return _generate_file('primitive_large_offsets', fields, batch_sizes) - - -def generate_null_case(batch_sizes): - # Interleave null with non-null types to ensure the appropriate number of - # buffers (0) is read and written - fields = [ - NullField(name='f0'), - get_field('f1', 'int32'), - NullField(name='f2'), - get_field('f3', 'float64'), - NullField(name='f4') - ] - return _generate_file('null', fields, batch_sizes) - - -def generate_null_trivial_case(batch_sizes): - # Generate a case with no buffers - fields = [ - NullField(name='f0'), - ] - return _generate_file('null_trivial', fields, batch_sizes) - - -def generate_decimal128_case(): - fields = [ - DecimalField(name='f{}'.format(i), precision=precision, scale=2, - bit_width=128) - for i, precision in enumerate(range(3, 39)) - ] - - possible_batch_sizes = 7, 10 - batch_sizes = [possible_batch_sizes[i % 2] for i in range(len(fields))] - # 'decimal' is the original name for the test, and it must match - # provide "gold" files that test backwards compatibility, so they - # can be appropriately skipped. - return _generate_file('decimal', fields, batch_sizes) - - -def generate_decimal256_case(): - fields = [ - DecimalField(name='f{}'.format(i), precision=precision, scale=5, - bit_width=256) - for i, precision in enumerate(range(37, 70)) - ] - - possible_batch_sizes = 7, 10 - batch_sizes = [possible_batch_sizes[i % 2] for i in range(len(fields))] - return _generate_file('decimal256', fields, batch_sizes) - - -def generate_datetime_case(): - fields = [ - DateField('f0', DateField.DAY), - DateField('f1', DateField.MILLISECOND), - TimeField('f2', 's'), - TimeField('f3', 'ms'), - TimeField('f4', 'us'), - TimeField('f5', 'ns'), - TimestampField('f6', 's'), - TimestampField('f7', 'ms'), - TimestampField('f8', 'us'), - TimestampField('f9', 'ns'), - TimestampField('f10', 'ms', tz=None), - TimestampField('f11', 's', tz='UTC'), - TimestampField('f12', 'ms', tz='US/Eastern'), - TimestampField('f13', 'us', tz='Europe/Paris'), - TimestampField('f14', 'ns', tz='US/Pacific'), - ] - - batch_sizes = [7, 10] - return _generate_file("datetime", fields, batch_sizes) - - -def generate_interval_case(): - fields = [ - DurationIntervalField('f1', 's'), - DurationIntervalField('f2', 'ms'), - DurationIntervalField('f3', 'us'), - DurationIntervalField('f4', 'ns'), - YearMonthIntervalField('f5'), - DayTimeIntervalField('f6'), - ] - - batch_sizes = [7, 10] - return _generate_file("interval", fields, batch_sizes) - - -def generate_map_case(): - fields = [ - MapField('map_nullable', get_field('key', 'utf8', nullable=False), - get_field('value', 'int32')), - ] - - batch_sizes = [7, 10] - return _generate_file("map", fields, batch_sizes) - - -def generate_non_canonical_map_case(): - fields = [ - MapField('map_other_names', - get_field('some_key', 'utf8', nullable=False), - get_field('some_value', 'int32'), - entries_name='some_entries'), - ] - - batch_sizes = [7] - return _generate_file("map_non_canonical", fields, batch_sizes) - - -def generate_nested_case(): - fields = [ - ListField('list_nullable', get_field('item', 'int32')), - FixedSizeListField('fixedsizelist_nullable', - get_field('item', 'int32'), 4), - StructField('struct_nullable', [get_field('f1', 'int32'), - get_field('f2', 'utf8')]), - # Fails on Go (ARROW-8452) - # ListField('list_nonnullable', get_field('item', 'int32'), - # nullable=False), - ] - - batch_sizes = [7, 10] - return _generate_file("nested", fields, batch_sizes) - - -def generate_recursive_nested_case(): - fields = [ - ListField('lists_list', - ListField('inner_list', get_field('item', 'int16'))), - ListField('structs_list', - StructField('inner_struct', - [get_field('f1', 'int32'), - get_field('f2', 'utf8')])), - ] - - batch_sizes = [7, 10] - return _generate_file("recursive_nested", fields, batch_sizes) - - -def generate_nested_large_offsets_case(): - fields = [ - LargeListField('large_list_nullable', get_field('item', 'int32')), - LargeListField('large_list_nonnullable', - get_field('item', 'int32'), nullable=False), - LargeListField('large_list_nested', - ListField('inner_list', get_field('item', 'int16'))), - ] - - batch_sizes = [0, 13] - return _generate_file("nested_large_offsets", fields, batch_sizes) - - -def generate_unions_case(): - fields = [ - SparseUnionField('sparse', [get_field('f1', 'int32'), - get_field('f2', 'utf8')], - type_ids=[5, 7]), - DenseUnionField('dense', [get_field('f1', 'int16'), - get_field('f2', 'binary')], - type_ids=[10, 20]), - SparseUnionField('sparse', [get_field('f1', 'float32', nullable=False), - get_field('f2', 'bool')], - type_ids=[5, 7], nullable=False), - DenseUnionField('dense', [get_field('f1', 'uint8', nullable=False), - get_field('f2', 'uint16'), - NullField('f3')], - type_ids=[42, 43, 44], nullable=False), - ] - - batch_sizes = [0, 11] - return _generate_file("union", fields, batch_sizes) - - -def generate_dictionary_case(): - dict0 = Dictionary(0, StringField('dictionary1'), size=10, name='DICT0') - dict1 = Dictionary(1, StringField('dictionary1'), size=5, name='DICT1') - dict2 = Dictionary(2, get_field('dictionary2', 'int64'), - size=50, name='DICT2') - - fields = [ - DictionaryField('dict0', get_field('', 'int8'), dict0), - DictionaryField('dict1', get_field('', 'int32'), dict1), - DictionaryField('dict2', get_field('', 'int16'), dict2) - ] - batch_sizes = [7, 10] - return _generate_file("dictionary", fields, batch_sizes, - dictionaries=[dict0, dict1, dict2]) - - -def generate_dictionary_unsigned_case(): - dict0 = Dictionary(0, StringField('dictionary0'), size=5, name='DICT0') - dict1 = Dictionary(1, StringField('dictionary1'), size=5, name='DICT1') - dict2 = Dictionary(2, StringField('dictionary2'), size=5, name='DICT2') - - # TODO: JavaScript does not support uint64 dictionary indices, so disabled - # for now - - # dict3 = Dictionary(3, StringField('dictionary3'), size=5, name='DICT3') - fields = [ - DictionaryField('f0', get_field('', 'uint8'), dict0), - DictionaryField('f1', get_field('', 'uint16'), dict1), - DictionaryField('f2', get_field('', 'uint32'), dict2), - # DictionaryField('f3', get_field('', 'uint64'), dict3) - ] - batch_sizes = [7, 10] - return _generate_file("dictionary_unsigned", fields, batch_sizes, - dictionaries=[dict0, dict1, dict2]) - - -def generate_nested_dictionary_case(): - dict0 = Dictionary(0, StringField('str'), size=10, name='DICT0') - - list_of_dict = ListField( - 'list', - DictionaryField('str_dict', get_field('', 'int8'), dict0)) - dict1 = Dictionary(1, list_of_dict, size=30, name='DICT1') - - struct_of_dict = StructField('struct', [ - DictionaryField('str_dict_a', get_field('', 'int8'), dict0), - DictionaryField('str_dict_b', get_field('', 'int8'), dict0) - ]) - dict2 = Dictionary(2, struct_of_dict, size=30, name='DICT2') - - fields = [ - DictionaryField('list_dict', get_field('', 'int8'), dict1), - DictionaryField('struct_dict', get_field('', 'int8'), dict2) - ] - - batch_sizes = [10, 13] - return _generate_file("nested_dictionary", fields, batch_sizes, - dictionaries=[dict0, dict1, dict2]) - - -def generate_extension_case(): - dict0 = Dictionary(0, StringField('dictionary0'), size=5, name='DICT0') - - uuid_type = ExtensionType('uuid', 'uuid-serialized', - FixedSizeBinaryField('', 16)) - dict_ext_type = ExtensionType( - 'dict-extension', 'dict-extension-serialized', - DictionaryField('str_dict', get_field('', 'int8'), dict0)) - - fields = [ - ExtensionField('uuids', uuid_type), - ExtensionField('dict_exts', dict_ext_type), - ] - - batch_sizes = [0, 13] - return _generate_file("extension", fields, batch_sizes, - dictionaries=[dict0]) - - -def get_generated_json_files(tempdir=None): - tempdir = tempdir or tempfile.mkdtemp(prefix='arrow-integration-') - - def _temp_path(): - return - - file_objs = [ - generate_primitive_case([], name='primitive_no_batches'), - generate_primitive_case([17, 20], name='primitive'), - generate_primitive_case([0, 0, 0], name='primitive_zerolength'), - - generate_primitive_large_offsets_case([17, 20]) - .skip_category('Go') - .skip_category('JS'), - - generate_null_case([10, 0]) - .skip_category('JS') # TODO(ARROW-7900) - .skip_category('Go'), # TODO(ARROW-7901) - - generate_null_trivial_case([0, 0]) - .skip_category('JS') # TODO(ARROW-7900) - .skip_category('Go'), # TODO(ARROW-7901) - - generate_decimal128_case() - .skip_category('Go') # TODO(ARROW-7948): Decimal + Go - .skip_category('Rust'), - - generate_decimal256_case() - .skip_category('Go') # TODO(ARROW-7948): Decimal + Go - .skip_category('JS') - .skip_category('Rust'), - - generate_datetime_case(), - - generate_interval_case() - .skip_category('JS') # TODO(ARROW-5239): Intervals + JS - .skip_category('Rust'), - - generate_map_case() - .skip_category('Go') # TODO(ARROW-5620): Map + Go - .skip_category('Rust'), - - generate_non_canonical_map_case() - .skip_category('Go') # TODO(ARROW-5620) - .skip_category('Java') # TODO(ARROW-8715) - .skip_category('JS') # TODO(ARROW-8716) - .skip_category('Rust'), - - generate_nested_case(), - - generate_recursive_nested_case() - .skip_category('Go'), # TODO(ARROW-8453) - - generate_nested_large_offsets_case() - .skip_category('Go') - .skip_category('JS') - .skip_category('Rust'), - - generate_unions_case() - .skip_category('Go') - .skip_category('JS') - .skip_category('Rust'), - - generate_custom_metadata_case() - .skip_category('Go') - .skip_category('JS'), - - generate_duplicate_fieldnames_case() - .skip_category('Go') - .skip_category('JS'), - - # TODO(ARROW-3039, ARROW-5267): Dictionaries in GO - generate_dictionary_case() - .skip_category('Go'), - - generate_dictionary_unsigned_case() - .skip_category('Go') # TODO(ARROW-9378) - .skip_category('Java'), # TODO(ARROW-9377) - - generate_nested_dictionary_case() - .skip_category('Go') - .skip_category('Java') # TODO(ARROW-7779) - .skip_category('JS') - .skip_category('Rust'), - - generate_extension_case() - .skip_category('Go') - .skip_category('JS') - .skip_category('Rust'), - ] - - generated_paths = [] - for file_obj in file_objs: - out_path = os.path.join(tempdir, 'generated_' + - file_obj.name + '.json') - file_obj.write(out_path) - generated_paths.append(file_obj) - - return generated_paths diff --git a/dev/archery/archery/integration/runner.py b/dev/archery/archery/integration/runner.py deleted file mode 100644 index 8aef16374907..000000000000 --- a/dev/archery/archery/integration/runner.py +++ /dev/null @@ -1,419 +0,0 @@ -# licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -from collections import namedtuple -from concurrent.futures import ThreadPoolExecutor -from functools import partial -import glob -import gzip -import itertools -import os -import sys -import tempfile -import traceback - -from .scenario import Scenario -from .tester_cpp import CPPTester -from .tester_go import GoTester -from .tester_rust import RustTester -from .tester_java import JavaTester -from .tester_js import JSTester -from .util import (ARROW_ROOT_DEFAULT, guid, SKIP_ARROW, SKIP_FLIGHT, - printer) -from . import datagen - - -Failure = namedtuple('Failure', - ('test_case', 'producer', 'consumer', 'exc_info')) - -log = printer.print - - -class Outcome: - def __init__(self): - self.failure = None - self.skipped = False - - -class IntegrationRunner(object): - - def __init__(self, json_files, flight_scenarios, testers, tempdir=None, - debug=False, stop_on_error=True, gold_dirs=None, - serial=False, match=None, **unused_kwargs): - self.json_files = json_files - self.flight_scenarios = flight_scenarios - self.testers = testers - self.temp_dir = tempdir or tempfile.mkdtemp() - self.debug = debug - self.stop_on_error = stop_on_error - self.serial = serial - self.gold_dirs = gold_dirs - self.failures = [] - self.match = match - - if self.match is not None: - print("-- Only running tests with {} in their name" - .format(self.match)) - self.json_files = [json_file for json_file in self.json_files - if self.match in json_file.name] - - def run(self): - """ - Run Arrow IPC integration tests for the matrix of enabled - implementations. - """ - for producer, consumer in itertools.product( - filter(lambda t: t.PRODUCER, self.testers), - filter(lambda t: t.CONSUMER, self.testers)): - self._compare_implementations( - producer, consumer, self._produce_consume, - self.json_files) - if self.gold_dirs: - for gold_dir, consumer in itertools.product( - self.gold_dirs, - filter(lambda t: t.CONSUMER, self.testers)): - log('\n\n\n\n') - log('******************************************************') - log('Tests against golden files in {}'.format(gold_dir)) - log('******************************************************') - - def run_gold(producer, consumer, outcome, test_case): - self._run_gold(gold_dir, producer, consumer, outcome, - test_case) - self._compare_implementations( - consumer, consumer, run_gold, - self._gold_tests(gold_dir)) - - def run_flight(self): - """ - Run Arrow Flight integration tests for the matrix of enabled - implementations. - """ - servers = filter(lambda t: t.FLIGHT_SERVER, self.testers) - clients = filter(lambda t: (t.FLIGHT_CLIENT and t.CONSUMER), - self.testers) - for server, client in itertools.product(servers, clients): - self._compare_flight_implementations(server, client) - - def _gold_tests(self, gold_dir): - prefix = os.path.basename(os.path.normpath(gold_dir)) - SUFFIX = ".json.gz" - golds = [jf for jf in os.listdir(gold_dir) if jf.endswith(SUFFIX)] - for json_path in golds: - name = json_path[json_path.index('_')+1: -len(SUFFIX)] - base_name = prefix + "_" + name + ".gold.json" - out_path = os.path.join(self.temp_dir, base_name) - with gzip.open(os.path.join(gold_dir, json_path)) as i: - with open(out_path, "wb") as out: - out.write(i.read()) - - try: - skip = next(f for f in self.json_files - if f.name == name).skip - except StopIteration: - skip = set() - if name == 'union' and prefix == '0.17.1': - skip.add("Java") - if prefix == '1.0.0-bigendian' or prefix == '1.0.0-littleendian': - skip.add("Go") - skip.add("Java") - skip.add("JS") - skip.add("Rust") - if prefix == '2.0.0-compression': - skip.add("JS") - skip.add("Rust") - - # See https://github.com/apache/arrow/pull/9822 for how to - # disable specific compression type tests. - - if prefix == '4.0.0-shareddict': - skip.add("Go") - - yield datagen.File(name, None, None, skip=skip, path=out_path) - - def _run_test_cases(self, producer, consumer, case_runner, - test_cases): - def case_wrapper(test_case): - with printer.cork(): - return case_runner(test_case) - - if self.failures and self.stop_on_error: - return - - if self.serial: - for outcome in map(case_wrapper, test_cases): - if outcome.failure is not None: - self.failures.append(outcome.failure) - if self.stop_on_error: - break - - else: - with ThreadPoolExecutor() as executor: - for outcome in executor.map(case_wrapper, test_cases): - if outcome.failure is not None: - self.failures.append(outcome.failure) - if self.stop_on_error: - break - - def _compare_implementations( - self, producer, consumer, run_binaries, test_cases): - """ - Compare Arrow IPC for two implementations (one producer, one consumer). - """ - log('##########################################################') - log('IPC: {0} producing, {1} consuming' - .format(producer.name, consumer.name)) - log('##########################################################') - - case_runner = partial(self._run_ipc_test_case, - producer, consumer, run_binaries) - self._run_test_cases(producer, consumer, case_runner, test_cases) - - def _run_ipc_test_case(self, producer, consumer, run_binaries, test_case): - """ - Run one IPC test case. - """ - outcome = Outcome() - - json_path = test_case.path - log('==========================================================') - log('Testing file {0}'.format(json_path)) - log('==========================================================') - - if producer.name in test_case.skip: - log('-- Skipping test because producer {0} does ' - 'not support'.format(producer.name)) - outcome.skipped = True - - elif consumer.name in test_case.skip: - log('-- Skipping test because consumer {0} does ' - 'not support'.format(consumer.name)) - outcome.skipped = True - - elif SKIP_ARROW in test_case.skip: - log('-- Skipping test') - outcome.skipped = True - - else: - try: - run_binaries(producer, consumer, outcome, test_case) - except Exception: - traceback.print_exc(file=printer.stdout) - outcome.failure = Failure(test_case, producer, consumer, - sys.exc_info()) - - return outcome - - def _produce_consume(self, producer, consumer, outcome, test_case): - # Make the random access file - json_path = test_case.path - file_id = guid()[:8] - name = os.path.splitext(os.path.basename(json_path))[0] - - producer_file_path = os.path.join(self.temp_dir, file_id + '_' + - name + '.json_as_file') - producer_stream_path = os.path.join(self.temp_dir, file_id + '_' + - name + '.producer_file_as_stream') - consumer_file_path = os.path.join(self.temp_dir, file_id + '_' + - name + '.consumer_stream_as_file') - - log('-- Creating binary inputs') - producer.json_to_file(json_path, producer_file_path) - - # Validate the file - log('-- Validating file') - consumer.validate(json_path, producer_file_path) - - log('-- Validating stream') - producer.file_to_stream(producer_file_path, producer_stream_path) - consumer.stream_to_file(producer_stream_path, consumer_file_path) - consumer.validate(json_path, consumer_file_path) - - def _run_gold(self, gold_dir, producer, consumer, outcome, test_case): - json_path = test_case.path - - # Validate the file - log('-- Validating file') - producer_file_path = os.path.join( - gold_dir, "generated_" + test_case.name + ".arrow_file") - consumer.validate(json_path, producer_file_path) - - log('-- Validating stream') - consumer_stream_path = os.path.join( - gold_dir, "generated_" + test_case.name + ".stream") - file_id = guid()[:8] - name = os.path.splitext(os.path.basename(json_path))[0] - - consumer_file_path = os.path.join(self.temp_dir, file_id + '_' + - name + '.consumer_stream_as_file') - - consumer.stream_to_file(consumer_stream_path, consumer_file_path) - consumer.validate(json_path, consumer_file_path) - - def _compare_flight_implementations(self, producer, consumer): - log('##########################################################') - log('Flight: {0} serving, {1} requesting' - .format(producer.name, consumer.name)) - log('##########################################################') - - case_runner = partial(self._run_flight_test_case, producer, consumer) - self._run_test_cases(producer, consumer, case_runner, - self.json_files + self.flight_scenarios) - - def _run_flight_test_case(self, producer, consumer, test_case): - """ - Run one Flight test case. - """ - outcome = Outcome() - - log('=' * 58) - log('Testing file {0}'.format(test_case.name)) - log('=' * 58) - - if producer.name in test_case.skip: - log('-- Skipping test because producer {0} does ' - 'not support'.format(producer.name)) - outcome.skipped = True - - elif consumer.name in test_case.skip: - log('-- Skipping test because consumer {0} does ' - 'not support'.format(consumer.name)) - outcome.skipped = True - - elif SKIP_FLIGHT in test_case.skip: - log('-- Skipping test') - outcome.skipped = True - - else: - try: - if isinstance(test_case, Scenario): - server = producer.flight_server(test_case.name) - client_args = {'scenario_name': test_case.name} - else: - server = producer.flight_server() - client_args = {'json_path': test_case.path} - - with server as port: - # Have the client upload the file, then download and - # compare - consumer.flight_request(port, **client_args) - except Exception: - traceback.print_exc(file=printer.stdout) - outcome.failure = Failure(test_case, producer, consumer, - sys.exc_info()) - - return outcome - - -def get_static_json_files(): - glob_pattern = os.path.join(ARROW_ROOT_DEFAULT, - 'integration', 'data', '*.json') - return [ - datagen.File(name=os.path.basename(p), path=p, skip=set(), - schema=None, batches=None) - for p in glob.glob(glob_pattern) - ] - - -def run_all_tests(with_cpp=True, with_java=True, with_js=True, - with_go=True, with_rust=False, run_flight=False, - tempdir=None, **kwargs): - tempdir = tempdir or tempfile.mkdtemp(prefix='arrow-integration-') - - testers = [] - - if with_cpp: - testers.append(CPPTester(**kwargs)) - - if with_java: - testers.append(JavaTester(**kwargs)) - - if with_js: - testers.append(JSTester(**kwargs)) - - if with_go: - testers.append(GoTester(**kwargs)) - - if with_rust: - testers.append(RustTester(**kwargs)) - - static_json_files = get_static_json_files() - generated_json_files = datagen.get_generated_json_files(tempdir=tempdir) - json_files = static_json_files + generated_json_files - - # Additional integration test cases for Arrow Flight. - flight_scenarios = [ - Scenario( - "auth:basic_proto", - description="Authenticate using the BasicAuth protobuf."), - Scenario( - "middleware", - description="Ensure headers are propagated via middleware.", - skip={"Rust"} # TODO(ARROW-10961): tonic upgrade needed - ), - ] - - runner = IntegrationRunner(json_files, flight_scenarios, testers, **kwargs) - runner.run() - if run_flight: - runner.run_flight() - - fail_count = 0 - if runner.failures: - log("################# FAILURES #################") - for test_case, producer, consumer, exc_info in runner.failures: - fail_count += 1 - log("FAILED TEST:", end=" ") - log(test_case.name, producer.name, "producing, ", - consumer.name, "consuming") - if exc_info: - traceback.print_exception(*exc_info) - log() - - log(fail_count, "failures") - if fail_count > 0: - sys.exit(1) - - -def write_js_test_json(directory): - datagen.generate_map_case().write( - os.path.join(directory, 'map.json') - ) - datagen.generate_nested_case().write( - os.path.join(directory, 'nested.json') - ) - datagen.generate_decimal_case().write( - os.path.join(directory, 'decimal.json') - ) - datagen.generate_datetime_case().write( - os.path.join(directory, 'datetime.json') - ) - datagen.generate_dictionary_case().write( - os.path.join(directory, 'dictionary.json') - ) - datagen.generate_dictionary_unsigned_case().write( - os.path.join(directory, 'dictionary_unsigned.json') - ) - datagen.generate_primitive_case([]).write( - os.path.join(directory, 'primitive_no_batches.json') - ) - datagen.generate_primitive_case([7, 10]).write( - os.path.join(directory, 'primitive.json') - ) - datagen.generate_primitive_case([0, 0, 0]).write( - os.path.join(directory, 'primitive-empty.json') - ) diff --git a/dev/archery/archery/integration/scenario.py b/dev/archery/archery/integration/scenario.py deleted file mode 100644 index 1fcbca64e6a1..000000000000 --- a/dev/archery/archery/integration/scenario.py +++ /dev/null @@ -1,29 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - - -class Scenario: - """ - An integration test scenario for Arrow Flight. - - Does not correspond to a particular IPC JSON file. - """ - - def __init__(self, name, description, skip=None): - self.name = name - self.description = description - self.skip = skip or set() diff --git a/dev/archery/archery/integration/tester.py b/dev/archery/archery/integration/tester.py deleted file mode 100644 index 122e4f2e4a78..000000000000 --- a/dev/archery/archery/integration/tester.py +++ /dev/null @@ -1,62 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -# Base class for language-specific integration test harnesses - -import subprocess - -from .util import log - - -class Tester(object): - PRODUCER = False - CONSUMER = False - FLIGHT_SERVER = False - FLIGHT_CLIENT = False - - def __init__(self, debug=False, **args): - self.args = args - self.debug = debug - - def run_shell_command(self, cmd): - cmd = ' '.join(cmd) - if self.debug: - log(cmd) - subprocess.check_call(cmd, shell=True) - - def json_to_file(self, json_path, arrow_path): - raise NotImplementedError - - def stream_to_file(self, stream_path, file_path): - raise NotImplementedError - - def file_to_stream(self, file_path, stream_path): - raise NotImplementedError - - def validate(self, json_path, arrow_path): - raise NotImplementedError - - def flight_server(self, scenario_name=None): - """Start the Flight server on a free port. - - This should be a context manager that returns the port as the - managed object, and cleans up the server on exit. - """ - raise NotImplementedError - - def flight_request(self, port, json_path=None, scenario_name=None): - raise NotImplementedError diff --git a/dev/archery/archery/integration/tester_cpp.py b/dev/archery/archery/integration/tester_cpp.py deleted file mode 100644 index d35c9550e58e..000000000000 --- a/dev/archery/archery/integration/tester_cpp.py +++ /dev/null @@ -1,116 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -import contextlib -import os -import subprocess - -from .tester import Tester -from .util import run_cmd, ARROW_ROOT_DEFAULT, log - - -class CPPTester(Tester): - PRODUCER = True - CONSUMER = True - FLIGHT_SERVER = True - FLIGHT_CLIENT = True - - EXE_PATH = os.environ.get( - 'ARROW_CPP_EXE_PATH', - os.path.join(ARROW_ROOT_DEFAULT, 'cpp/build/debug')) - - CPP_INTEGRATION_EXE = os.path.join(EXE_PATH, 'arrow-json-integration-test') - STREAM_TO_FILE = os.path.join(EXE_PATH, 'arrow-stream-to-file') - FILE_TO_STREAM = os.path.join(EXE_PATH, 'arrow-file-to-stream') - - FLIGHT_SERVER_CMD = [ - os.path.join(EXE_PATH, 'flight-test-integration-server')] - FLIGHT_CLIENT_CMD = [ - os.path.join(EXE_PATH, 'flight-test-integration-client'), - "-host", "localhost"] - - name = 'C++' - - def _run(self, arrow_path=None, json_path=None, command='VALIDATE'): - cmd = [self.CPP_INTEGRATION_EXE, '--integration'] - - if arrow_path is not None: - cmd.append('--arrow=' + arrow_path) - - if json_path is not None: - cmd.append('--json=' + json_path) - - cmd.append('--mode=' + command) - - if self.debug: - log(' '.join(cmd)) - - run_cmd(cmd) - - def validate(self, json_path, arrow_path): - return self._run(arrow_path, json_path, 'VALIDATE') - - def json_to_file(self, json_path, arrow_path): - return self._run(arrow_path, json_path, 'JSON_TO_ARROW') - - def stream_to_file(self, stream_path, file_path): - cmd = [self.STREAM_TO_FILE, '<', stream_path, '>', file_path] - self.run_shell_command(cmd) - - def file_to_stream(self, file_path, stream_path): - cmd = [self.FILE_TO_STREAM, file_path, '>', stream_path] - self.run_shell_command(cmd) - - @contextlib.contextmanager - def flight_server(self, scenario_name=None): - cmd = self.FLIGHT_SERVER_CMD + ['-port=0'] - if scenario_name: - cmd = cmd + ["-scenario", scenario_name] - if self.debug: - log(' '.join(cmd)) - server = subprocess.Popen(cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - try: - output = server.stdout.readline().decode() - if not output.startswith("Server listening on localhost:"): - server.kill() - out, err = server.communicate() - raise RuntimeError( - "Flight-C++ server did not start properly, " - "stdout:\n{}\n\nstderr:\n{}\n" - .format(output + out.decode(), err.decode())) - port = int(output.split(":")[1]) - yield port - finally: - server.kill() - server.wait(5) - - def flight_request(self, port, json_path=None, scenario_name=None): - cmd = self.FLIGHT_CLIENT_CMD + [ - '-port=' + str(port), - ] - if json_path: - cmd.extend(('-path', json_path)) - elif scenario_name: - cmd.extend(('-scenario', scenario_name)) - else: - raise TypeError("Must provide one of json_path or scenario_name") - - if self.debug: - log(' '.join(cmd)) - run_cmd(cmd) diff --git a/dev/archery/archery/integration/tester_go.py b/dev/archery/archery/integration/tester_go.py deleted file mode 100644 index ea799c5a1bd2..000000000000 --- a/dev/archery/archery/integration/tester_go.py +++ /dev/null @@ -1,67 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -import os - -from .tester import Tester -from .util import run_cmd, log - - -class GoTester(Tester): - PRODUCER = True - CONSUMER = True - - # FIXME(sbinet): revisit for Go modules - HOME = os.getenv('HOME', '~') - GOPATH = os.getenv('GOPATH', os.path.join(HOME, 'go')) - GOBIN = os.environ.get('GOBIN', os.path.join(GOPATH, 'bin')) - - GO_INTEGRATION_EXE = os.path.join(GOBIN, 'arrow-json-integration-test') - STREAM_TO_FILE = os.path.join(GOBIN, 'arrow-stream-to-file') - FILE_TO_STREAM = os.path.join(GOBIN, 'arrow-file-to-stream') - - name = 'Go' - - def _run(self, arrow_path=None, json_path=None, command='VALIDATE'): - cmd = [self.GO_INTEGRATION_EXE] - - if arrow_path is not None: - cmd.extend(['-arrow', arrow_path]) - - if json_path is not None: - cmd.extend(['-json', json_path]) - - cmd.extend(['-mode', command]) - - if self.debug: - log(' '.join(cmd)) - - run_cmd(cmd) - - def validate(self, json_path, arrow_path): - return self._run(arrow_path, json_path, 'VALIDATE') - - def json_to_file(self, json_path, arrow_path): - return self._run(arrow_path, json_path, 'JSON_TO_ARROW') - - def stream_to_file(self, stream_path, file_path): - cmd = [self.STREAM_TO_FILE, '<', stream_path, '>', file_path] - self.run_shell_command(cmd) - - def file_to_stream(self, file_path, stream_path): - cmd = [self.FILE_TO_STREAM, file_path, '>', stream_path] - self.run_shell_command(cmd) diff --git a/dev/archery/archery/integration/tester_java.py b/dev/archery/archery/integration/tester_java.py deleted file mode 100644 index f283f6cd255c..000000000000 --- a/dev/archery/archery/integration/tester_java.py +++ /dev/null @@ -1,140 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -import contextlib -import os -import subprocess - -from .tester import Tester -from .util import run_cmd, ARROW_ROOT_DEFAULT, log - - -def load_version_from_pom(): - import xml.etree.ElementTree as ET - tree = ET.parse(os.path.join(ARROW_ROOT_DEFAULT, 'java', 'pom.xml')) - tag_pattern = '{http://maven.apache.org/POM/4.0.0}version' - version_tag = list(tree.getroot().findall(tag_pattern))[0] - return version_tag.text - - -class JavaTester(Tester): - PRODUCER = True - CONSUMER = True - FLIGHT_SERVER = True - FLIGHT_CLIENT = True - - JAVA_OPTS = ['-Dio.netty.tryReflectionSetAccessible=true', - '-Darrow.struct.conflict.policy=CONFLICT_APPEND'] - - _arrow_version = load_version_from_pom() - ARROW_TOOLS_JAR = os.environ.get( - 'ARROW_JAVA_INTEGRATION_JAR', - os.path.join(ARROW_ROOT_DEFAULT, - 'java/tools/target/arrow-tools-{}-' - 'jar-with-dependencies.jar'.format(_arrow_version))) - ARROW_FLIGHT_JAR = os.environ.get( - 'ARROW_FLIGHT_JAVA_INTEGRATION_JAR', - os.path.join(ARROW_ROOT_DEFAULT, - 'java/flight/flight-core/target/flight-core-{}-' - 'jar-with-dependencies.jar'.format(_arrow_version))) - ARROW_FLIGHT_SERVER = ('org.apache.arrow.flight.example.integration.' - 'IntegrationTestServer') - ARROW_FLIGHT_CLIENT = ('org.apache.arrow.flight.example.integration.' - 'IntegrationTestClient') - - name = 'Java' - - def _run(self, arrow_path=None, json_path=None, command='VALIDATE'): - cmd = ['java'] + self.JAVA_OPTS + \ - ['-cp', self.ARROW_TOOLS_JAR, 'org.apache.arrow.tools.Integration'] - - if arrow_path is not None: - cmd.extend(['-a', arrow_path]) - - if json_path is not None: - cmd.extend(['-j', json_path]) - - cmd.extend(['-c', command]) - - if self.debug: - log(' '.join(cmd)) - - run_cmd(cmd) - - def validate(self, json_path, arrow_path): - return self._run(arrow_path, json_path, 'VALIDATE') - - def json_to_file(self, json_path, arrow_path): - return self._run(arrow_path, json_path, 'JSON_TO_ARROW') - - def stream_to_file(self, stream_path, file_path): - cmd = ['java'] + self.JAVA_OPTS + \ - ['-cp', self.ARROW_TOOLS_JAR, - 'org.apache.arrow.tools.StreamToFile', stream_path, file_path] - if self.debug: - log(' '.join(cmd)) - run_cmd(cmd) - - def file_to_stream(self, file_path, stream_path): - cmd = ['java'] + self.JAVA_OPTS + \ - ['-cp', self.ARROW_TOOLS_JAR, - 'org.apache.arrow.tools.FileToStream', file_path, stream_path] - if self.debug: - log(' '.join(cmd)) - run_cmd(cmd) - - def flight_request(self, port, json_path=None, scenario_name=None): - cmd = ['java'] + self.JAVA_OPTS + \ - ['-cp', self.ARROW_FLIGHT_JAR, self.ARROW_FLIGHT_CLIENT, - '-port', str(port)] - - if json_path: - cmd.extend(('-j', json_path)) - elif scenario_name: - cmd.extend(('-scenario', scenario_name)) - else: - raise TypeError("Must provide one of json_path or scenario_name") - - if self.debug: - log(' '.join(cmd)) - run_cmd(cmd) - - @contextlib.contextmanager - def flight_server(self, scenario_name=None): - cmd = ['java'] + self.JAVA_OPTS + \ - ['-cp', self.ARROW_FLIGHT_JAR, self.ARROW_FLIGHT_SERVER, - '-port', '0'] - if scenario_name: - cmd.extend(('-scenario', scenario_name)) - if self.debug: - log(' '.join(cmd)) - server = subprocess.Popen(cmd, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - try: - output = server.stdout.readline().decode() - if not output.startswith("Server listening on localhost:"): - server.kill() - out, err = server.communicate() - raise RuntimeError( - "Flight-Java server did not start properly, " - "stdout:\n{}\n\nstderr:\n{}\n" - .format(output + out.decode(), err.decode())) - port = int(output.split(":")[1]) - yield port - finally: - server.kill() - server.wait(5) diff --git a/dev/archery/archery/integration/tester_js.py b/dev/archery/archery/integration/tester_js.py deleted file mode 100644 index e24eec0cadaa..000000000000 --- a/dev/archery/archery/integration/tester_js.py +++ /dev/null @@ -1,73 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -import os - -from .tester import Tester -from .util import run_cmd, ARROW_ROOT_DEFAULT, log - - -class JSTester(Tester): - PRODUCER = True - CONSUMER = True - - EXE_PATH = os.path.join(ARROW_ROOT_DEFAULT, 'js/bin') - VALIDATE = os.path.join(EXE_PATH, 'integration.js') - JSON_TO_ARROW = os.path.join(EXE_PATH, 'json-to-arrow.js') - STREAM_TO_FILE = os.path.join(EXE_PATH, 'stream-to-file.js') - FILE_TO_STREAM = os.path.join(EXE_PATH, 'file-to-stream.js') - - name = 'JS' - - def _run(self, exe_cmd, arrow_path=None, json_path=None, - command='VALIDATE'): - cmd = [exe_cmd] - - if arrow_path is not None: - cmd.extend(['-a', arrow_path]) - - if json_path is not None: - cmd.extend(['-j', json_path]) - - cmd.extend(['--mode', command]) - - if self.debug: - log(' '.join(cmd)) - - run_cmd(cmd) - - def validate(self, json_path, arrow_path): - return self._run(self.VALIDATE, arrow_path, json_path, 'VALIDATE') - - def json_to_file(self, json_path, arrow_path): - cmd = ['node', - '--no-warnings', self.JSON_TO_ARROW, - '-a', arrow_path, - '-j', json_path] - self.run_shell_command(cmd) - - def stream_to_file(self, stream_path, file_path): - cmd = ['node', '--no-warnings', self.STREAM_TO_FILE, - '<', stream_path, - '>', file_path] - self.run_shell_command(cmd) - - def file_to_stream(self, file_path, stream_path): - cmd = ['node', '--no-warnings', self.FILE_TO_STREAM, - '<', file_path, - '>', stream_path] - self.run_shell_command(cmd) diff --git a/dev/archery/archery/integration/tester_rust.py b/dev/archery/archery/integration/tester_rust.py deleted file mode 100644 index bca80ebae3c6..000000000000 --- a/dev/archery/archery/integration/tester_rust.py +++ /dev/null @@ -1,115 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -import contextlib -import os -import subprocess - -from .tester import Tester -from .util import run_cmd, ARROW_ROOT_DEFAULT, log - - -class RustTester(Tester): - PRODUCER = True - CONSUMER = True - FLIGHT_SERVER = True - FLIGHT_CLIENT = True - - EXE_PATH = os.path.join(ARROW_ROOT_DEFAULT, 'rust/target/debug') - - RUST_INTEGRATION_EXE = os.path.join(EXE_PATH, - 'arrow-json-integration-test') - STREAM_TO_FILE = os.path.join(EXE_PATH, 'arrow-stream-to-file') - FILE_TO_STREAM = os.path.join(EXE_PATH, 'arrow-file-to-stream') - - FLIGHT_SERVER_CMD = [ - os.path.join(EXE_PATH, 'flight-test-integration-server')] - FLIGHT_CLIENT_CMD = [ - os.path.join(EXE_PATH, 'flight-test-integration-client'), - "--host", "localhost"] - - name = 'Rust' - - def _run(self, arrow_path=None, json_path=None, command='VALIDATE'): - cmd = [self.RUST_INTEGRATION_EXE, '--integration'] - - if arrow_path is not None: - cmd.append('--arrow=' + arrow_path) - - if json_path is not None: - cmd.append('--json=' + json_path) - - cmd.append('--mode=' + command) - - if self.debug: - log(' '.join(cmd)) - - run_cmd(cmd) - - def validate(self, json_path, arrow_path): - return self._run(arrow_path, json_path, 'VALIDATE') - - def json_to_file(self, json_path, arrow_path): - return self._run(arrow_path, json_path, 'JSON_TO_ARROW') - - def stream_to_file(self, stream_path, file_path): - cmd = [self.STREAM_TO_FILE, '<', stream_path, '>', file_path] - self.run_shell_command(cmd) - - def file_to_stream(self, file_path, stream_path): - cmd = [self.FILE_TO_STREAM, file_path, '>', stream_path] - self.run_shell_command(cmd) - - @contextlib.contextmanager - def flight_server(self, scenario_name=None): - cmd = self.FLIGHT_SERVER_CMD + ['--port=0'] - if scenario_name: - cmd = cmd + ["--scenario", scenario_name] - if self.debug: - log(' '.join(cmd)) - server = subprocess.Popen(cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - try: - output = server.stdout.readline().decode() - if not output.startswith("Server listening on localhost:"): - server.kill() - out, err = server.communicate() - raise RuntimeError( - "Flight-Rust server did not start properly, " - "stdout:\n{}\n\nstderr:\n{}\n" - .format(output + out.decode(), err.decode())) - port = int(output.split(":")[1]) - yield port - finally: - server.kill() - server.wait(5) - - def flight_request(self, port, json_path=None, scenario_name=None): - cmd = self.FLIGHT_CLIENT_CMD + [ - '--port=' + str(port), - ] - if json_path: - cmd.extend(('--path', json_path)) - elif scenario_name: - cmd.extend(('--scenario', scenario_name)) - else: - raise TypeError("Must provide one of json_path or scenario_name") - - if self.debug: - log(' '.join(cmd)) - run_cmd(cmd) diff --git a/dev/archery/archery/integration/util.py b/dev/archery/archery/integration/util.py deleted file mode 100644 index a4c4982ecb38..000000000000 --- a/dev/archery/archery/integration/util.py +++ /dev/null @@ -1,166 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -import contextlib -import io -import os -import random -import socket -import subprocess -import sys -import threading -import uuid - -import numpy as np - - -def guid(): - return uuid.uuid4().hex - - -# SKIP categories -SKIP_ARROW = 'arrow' -SKIP_FLIGHT = 'flight' - -ARROW_ROOT_DEFAULT = os.environ.get( - 'ARROW_ROOT', - os.path.abspath(__file__).rsplit("/", 5)[0] -) - - -class _Printer: - """ - A print()-providing object that can override the stream output on - a per-thread basis. - """ - - def __init__(self): - self._tls = threading.local() - - def _get_stdout(self): - try: - return self._tls.stdout - except AttributeError: - self._tls.stdout = sys.stdout - self._tls.corked = False - return self._tls.stdout - - def print(self, *args, **kwargs): - """ - A variant of print() that writes to a thread-local stream. - """ - print(*args, file=self._get_stdout(), **kwargs) - - @property - def stdout(self): - """ - A thread-local stdout wrapper that may be temporarily buffered - using `cork()`. - """ - return self._get_stdout() - - @contextlib.contextmanager - def cork(self): - """ - Temporarily buffer this thread's stream and write out its contents - at the end of the context manager. Useful to avoid interleaved - output when multiple threads output progress information. - """ - outer_stdout = self._get_stdout() - assert not self._tls.corked, "reentrant call" - inner_stdout = self._tls.stdout = io.StringIO() - self._tls.corked = True - try: - yield - finally: - self._tls.stdout = outer_stdout - self._tls.corked = False - outer_stdout.write(inner_stdout.getvalue()) - outer_stdout.flush() - - -printer = _Printer() -log = printer.print - - -_RAND_CHARS = np.array(list("abcdefghijklmnop123456Ârrôwµ£°€矢"), dtype="U") - - -def random_utf8(nchars): - """ - Generate one random UTF8 string. - """ - return ''.join(np.random.choice(_RAND_CHARS, nchars)) - - -def random_bytes(nbytes): - """ - Generate one random binary string. - """ - # NOTE getrandbits(0) fails - if nbytes > 0: - return random.getrandbits(nbytes * 8).to_bytes(nbytes, - byteorder='little') - else: - return b"" - - -def tobytes(o): - if isinstance(o, str): - return o.encode('utf8') - return o - - -def frombytes(o): - if isinstance(o, bytes): - return o.decode('utf8') - return o - - -def run_cmd(cmd): - if isinstance(cmd, str): - cmd = cmd.split(' ') - - try: - output = subprocess.check_output(cmd, stderr=subprocess.STDOUT) - except subprocess.CalledProcessError as e: - # this avoids hiding the stdout / stderr of failed processes - sio = io.StringIO() - print('Command failed:', " ".join(cmd), file=sio) - print('With output:', file=sio) - print('--------------', file=sio) - print(frombytes(e.output), file=sio) - print('--------------', file=sio) - raise RuntimeError(sio.getvalue()) - - return frombytes(output) - - -# Adapted from CPython -def find_unused_port(family=socket.AF_INET, socktype=socket.SOCK_STREAM): - """Returns an unused port that should be suitable for binding. This is - achieved by creating a temporary socket with the same family and type as - the 'sock' parameter (default is AF_INET, SOCK_STREAM), and binding it to - the specified host address (defaults to 0.0.0.0) with the port set to 0, - eliciting an unused ephemeral port from the OS. The temporary socket is - then closed and deleted, and the ephemeral port is returned. - """ - with socket.socket(family, socktype) as tempsock: - tempsock.bind(('', 0)) - port = tempsock.getsockname()[1] - del tempsock - return port diff --git a/dev/archery/archery/lang/__init__.py b/dev/archery/archery/lang/__init__.py deleted file mode 100644 index 13a83393a912..000000000000 --- a/dev/archery/archery/lang/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. diff --git a/dev/archery/archery/lang/cpp.py b/dev/archery/archery/lang/cpp.py deleted file mode 100644 index 045d23b56b15..000000000000 --- a/dev/archery/archery/lang/cpp.py +++ /dev/null @@ -1,295 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -import os - -from ..utils.cmake import CMakeDefinition - - -def truthifier(value): - return "ON" if value else "OFF" - - -def or_else(value, default): - return value if value else default - - -def coalesce(value, fallback): - return fallback if value is None else value - - -LLVM_VERSION = 7 - - -class CppConfiguration: - def __init__(self, - - # toolchain - cc=None, cxx=None, cxx_flags=None, - build_type=None, warn_level=None, - cpp_package_prefix=None, install_prefix=None, use_conda=None, - build_static=False, build_shared=True, - # tests & examples - with_tests=None, with_benchmarks=None, with_examples=None, - with_integration=None, - # static checks - use_asan=None, use_tsan=None, use_ubsan=None, - with_fuzzing=None, - # Components - with_compute=None, with_csv=None, with_cuda=None, - with_dataset=None, with_filesystem=None, with_flight=None, - with_gandiva=None, with_hdfs=None, with_hiveserver2=None, - with_ipc=True, with_json=None, with_jni=None, - with_mimalloc=None, - with_parquet=None, with_plasma=None, with_python=True, - with_r=None, with_s3=None, - # Compressions - with_brotli=None, with_bz2=None, with_lz4=None, - with_snappy=None, with_zlib=None, with_zstd=None, - # extras - with_lint_only=False, - use_gold_linker=True, - simd_level="SSE4_2", - cmake_extras=None): - self._cc = cc - self._cxx = cxx - self.cxx_flags = cxx_flags - - self._build_type = build_type - self.warn_level = warn_level - self._install_prefix = install_prefix - self._package_prefix = cpp_package_prefix - self._use_conda = use_conda - self.build_static = build_static - self.build_shared = build_shared - - self.with_tests = with_tests - self.with_benchmarks = with_benchmarks - self.with_examples = with_examples - self.with_integration = with_integration - - self.use_asan = use_asan - self.use_tsan = use_tsan - self.use_ubsan = use_ubsan - self.with_fuzzing = with_fuzzing - - self.with_compute = with_compute - self.with_csv = with_csv - self.with_cuda = with_cuda - self.with_dataset = with_dataset - self.with_filesystem = with_filesystem - self.with_flight = with_flight - self.with_gandiva = with_gandiva - self.with_hdfs = with_hdfs - self.with_hiveserver2 = with_hiveserver2 - self.with_ipc = with_ipc - self.with_json = with_json - self.with_jni = with_jni - self.with_mimalloc = with_mimalloc - self.with_parquet = with_parquet - self.with_plasma = with_plasma - self.with_python = with_python - self.with_r = with_r - self.with_s3 = with_s3 - - self.with_brotli = with_brotli - self.with_bz2 = with_bz2 - self.with_lz4 = with_lz4 - self.with_snappy = with_snappy - self.with_zlib = with_zlib - self.with_zstd = with_zstd - - self.with_lint_only = with_lint_only - self.use_gold_linker = use_gold_linker - self.simd_level = simd_level - - self.cmake_extras = cmake_extras - - # Fixup required dependencies by providing sane defaults if the caller - # didn't specify the option. - if self.with_r: - self.with_csv = coalesce(with_csv, True) - self.with_dataset = coalesce(with_dataset, True) - self.with_filesystem = coalesce(with_filesystem, True) - self.with_ipc = coalesce(with_ipc, True) - self.with_json = coalesce(with_json, True) - self.with_parquet = coalesce(with_parquet, True) - - if self.with_python: - self.with_zlib = coalesce(with_zlib, True) - self.with_lz4 = coalesce(with_lz4, True) - - if self.with_dataset: - self.with_filesystem = coalesce(with_filesystem, True) - self.with_parquet = coalesce(with_parquet, True) - - if self.with_parquet: - self.with_snappy = coalesce(with_snappy, True) - - @property - def build_type(self): - if self._build_type: - return self._build_type - - if self.with_fuzzing: - return "relwithdebinfo" - - return "release" - - @property - def cc(self): - if self._cc: - return self._cc - - if self.with_fuzzing: - return "clang-{}".format(LLVM_VERSION) - - return None - - @property - def cxx(self): - if self._cxx: - return self._cxx - - if self.with_fuzzing: - return "clang++-{}".format(LLVM_VERSION) - - return None - - def _gen_defs(self): - if self.cxx_flags: - yield ("ARROW_CXXFLAGS", self.cxx_flags) - - yield ("CMAKE_EXPORT_COMPILE_COMMANDS", truthifier(True)) - yield ("CMAKE_BUILD_TYPE", self.build_type) - yield ("CMAKE_UNITY_BUILD", True) - - if not self.with_lint_only: - yield ("BUILD_WARNING_LEVEL", - or_else(self.warn_level, "production")) - - # if not ctx.quiet: - # yield ("ARROW_VERBOSE_THIRDPARTY_BUILD", "ON") - - maybe_prefix = self.install_prefix - if maybe_prefix: - yield ("CMAKE_INSTALL_PREFIX", maybe_prefix) - - if self._package_prefix is not None: - yield ("ARROW_DEPENDENCY_SOURCE", "SYSTEM") - yield ("ARROW_PACKAGE_PREFIX", self._package_prefix) - - yield ("ARROW_BUILD_STATIC", truthifier(self.build_static)) - yield ("ARROW_BUILD_SHARED", truthifier(self.build_shared)) - - # Tests and benchmarks - yield ("ARROW_BUILD_TESTS", truthifier(self.with_tests)) - yield ("ARROW_BUILD_BENCHMARKS", truthifier(self.with_benchmarks)) - yield ("ARROW_BUILD_EXAMPLES", truthifier(self.with_examples)) - yield ("ARROW_BUILD_INTEGRATION", truthifier(self.with_integration)) - - # Static checks - yield ("ARROW_USE_ASAN", truthifier(self.use_asan)) - yield ("ARROW_USE_TSAN", truthifier(self.use_tsan)) - yield ("ARROW_USE_UBSAN", truthifier(self.use_ubsan)) - yield ("ARROW_FUZZING", truthifier(self.with_fuzzing)) - - # Components - yield ("ARROW_COMPUTE", truthifier(self.with_compute)) - yield ("ARROW_CSV", truthifier(self.with_csv)) - yield ("ARROW_CUDA", truthifier(self.with_cuda)) - yield ("ARROW_DATASET", truthifier(self.with_dataset)) - yield ("ARROW_FILESYSTEM", truthifier(self.with_filesystem)) - yield ("ARROW_FLIGHT", truthifier(self.with_flight)) - yield ("ARROW_GANDIVA", truthifier(self.with_gandiva)) - yield ("ARROW_PARQUET", truthifier(self.with_parquet)) - yield ("ARROW_HDFS", truthifier(self.with_hdfs)) - yield ("ARROW_HIVESERVER2", truthifier(self.with_hiveserver2)) - yield ("ARROW_IPC", truthifier(self.with_ipc)) - yield ("ARROW_JSON", truthifier(self.with_json)) - yield ("ARROW_JNI", truthifier(self.with_jni)) - yield ("ARROW_MIMALLOC", truthifier(self.with_mimalloc)) - yield ("ARROW_PLASMA", truthifier(self.with_plasma)) - yield ("ARROW_PYTHON", truthifier(self.with_python)) - yield ("ARROW_S3", truthifier(self.with_s3)) - - # Compressions - yield ("ARROW_WITH_BROTLI", truthifier(self.with_brotli)) - yield ("ARROW_WITH_BZ2", truthifier(self.with_bz2)) - yield ("ARROW_WITH_LZ4", truthifier(self.with_lz4)) - yield ("ARROW_WITH_SNAPPY", truthifier(self.with_snappy)) - yield ("ARROW_WITH_ZLIB", truthifier(self.with_zlib)) - yield ("ARROW_WITH_ZSTD", truthifier(self.with_zstd)) - - yield ("ARROW_LINT_ONLY", truthifier(self.with_lint_only)) - - # Some configurations don't like gnu gold linker. - broken_with_gold_ld = [self.with_fuzzing, self.with_gandiva] - if self.use_gold_linker and not any(broken_with_gold_ld): - yield ("ARROW_USE_LD_GOLD", truthifier(self.use_gold_linker)) - yield ("ARROW_SIMD_LEVEL", or_else(self.simd_level, "SSE4_2")) - - # Detect custom conda toolchain - if self.use_conda: - for d, v in [('CMAKE_AR', 'AR'), ('CMAKE_RANLIB', 'RANLIB')]: - v = os.environ.get(v) - if v: - yield (d, v) - - @property - def install_prefix(self): - if self._install_prefix: - return self._install_prefix - - if self.use_conda: - return os.environ.get("CONDA_PREFIX") - - return None - - @property - def use_conda(self): - # If the user didn't specify a preference, guess via environment - if self._use_conda is None: - return os.environ.get("CONDA_PREFIX") is not None - - return self._use_conda - - @property - def definitions(self): - extras = list(self.cmake_extras) if self.cmake_extras else [] - definitions = ["-D{}={}".format(d[0], d[1]) for d in self._gen_defs()] - return definitions + extras - - @property - def environment(self): - env = os.environ.copy() - - if self.cc: - env["CC"] = self.cc - - if self.cxx: - env["CXX"] = self.cxx - - return env - - -class CppCMakeDefinition(CMakeDefinition): - def __init__(self, source, conf, **kwargs): - self.configuration = conf - super().__init__(source, **kwargs, - definitions=conf.definitions, env=conf.environment, - build_type=conf.build_type) diff --git a/dev/archery/archery/lang/java.py b/dev/archery/archery/lang/java.py deleted file mode 100644 index 24743b67fd74..000000000000 --- a/dev/archery/archery/lang/java.py +++ /dev/null @@ -1,30 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -from ..utils.command import Command, CommandStackMixin, default_bin - - -class Java(Command): - def __init__(self, java_bin=None): - self.bin = default_bin(java_bin, "java") - - -class Jar(CommandStackMixin, Java): - def __init__(self, jar, *args, **kwargs): - self.jar = jar - self.argv = ("-jar", jar) - Java.__init__(self, *args, **kwargs) diff --git a/dev/archery/archery/lang/python.py b/dev/archery/archery/lang/python.py deleted file mode 100644 index 4952d5f23051..000000000000 --- a/dev/archery/archery/lang/python.py +++ /dev/null @@ -1,218 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -import inspect -import tokenize -from contextlib import contextmanager - -try: - from numpydoc.validate import Docstring, validate -except ImportError: - have_numpydoc = False -else: - have_numpydoc = True - -from ..utils.command import Command, capture_stdout, default_bin - - -class Flake8(Command): - def __init__(self, flake8_bin=None): - self.bin = default_bin(flake8_bin, "flake8") - - -class Autopep8(Command): - def __init__(self, autopep8_bin=None): - self.bin = default_bin(autopep8_bin, "autopep8") - - @capture_stdout() - def run_captured(self, *args, **kwargs): - return self.run(*args, **kwargs) - - -def _tokenize_signature(s): - lines = s.encode('ascii').splitlines() - generator = iter(lines).__next__ - return tokenize.tokenize(generator) - - -def _convert_typehint(tokens): - names = [] - opening_bracket_reached = False - for token in tokens: - # omit the tokens before the opening bracket - if not opening_bracket_reached: - if token.string == '(': - opening_bracket_reached = True - else: - continue - - if token.type == 1: # type 1 means NAME token - names.append(token) - else: - if len(names) == 1: - yield (names[0].type, names[0].string) - elif len(names) == 2: - # two "NAME" tokens follow each other which means a cython - # typehint like `bool argument`, so remove the typehint - # note that we could convert it to python typehints, but hints - # are not supported by _signature_fromstr - yield (names[1].type, names[1].string) - elif len(names) > 2: - raise ValueError('More than two NAME tokens follow each other') - names = [] - yield (token.type, token.string) - - -def inspect_signature(obj): - """ - Custom signature inspection primarily for cython generated callables. - - Cython puts the signatures to the first line of the docstrings, which we - can reuse to parse the python signature from, but some gymnastics are - required, like removing the cython typehints. - - It converts the cython signature: - array(obj, type=None, mask=None, size=None, from_pandas=None, - bool safe=True, MemoryPool memory_pool=None) - To: - - """ - cython_signature = obj.__doc__.splitlines()[0] - cython_tokens = _tokenize_signature(cython_signature) - python_tokens = _convert_typehint(cython_tokens) - python_signature = tokenize.untokenize(python_tokens) - return inspect._signature_fromstr(inspect.Signature, obj, python_signature) - - -class NumpyDoc: - - def __init__(self, symbols=None): - if not have_numpydoc: - raise RuntimeError( - 'Numpydoc is not available, install the development version ' - 'with command: pip install ' - 'git+https://github.com/numpy/numpydoc' - ) - self.symbols = set(symbols or {'pyarrow'}) - - def traverse(self, fn, obj, from_package): - """Apply a function on publicly exposed API components. - - Recursively iterates over the members of the passed object. It omits - any '_' prefixed and thirdparty (non pyarrow) symbols. - - Parameters - ---------- - obj : Any - from_package : string, default 'pyarrow' - Predicate to only consider objects from this package. - """ - todo = [obj] - seen = set() - - while todo: - obj = todo.pop() - if obj in seen: - continue - else: - seen.add(obj) - - fn(obj) - - for name in dir(obj): - if name.startswith('_'): - continue - - member = getattr(obj, name) - module = getattr(member, '__module__', None) - if not (module and module.startswith(from_package)): - continue - - todo.append(member) - - @contextmanager - def _apply_patches(self): - """ - Patch Docstring class to bypass loading already loaded python objects. - """ - orig_load_obj = Docstring._load_obj - orig_signature = inspect.signature - - @staticmethod - def _load_obj(obj): - # By default it expects a qualname and import the object, but we - # have already loaded object after the API traversal. - if isinstance(obj, str): - return orig_load_obj(obj) - else: - return obj - - def signature(obj): - # inspect.signature tries to parse __text_signature__ if other - # properties like __signature__ doesn't exists, but cython - # doesn't set that property despite that embedsignature cython - # directive is set. The only way to inspect a cython compiled - # callable's signature to parse it from __doc__ while - # embedsignature directive is set during the build phase. - # So path inspect.signature function to attempt to parse the first - # line of callable.__doc__ as a signature. - try: - return orig_signature(obj) - except Exception as orig_error: - try: - return inspect_signature(obj) - except Exception: - raise orig_error - - try: - Docstring._load_obj = _load_obj - inspect.signature = signature - yield - finally: - Docstring._load_obj = orig_load_obj - inspect.signature = orig_signature - - def validate(self, from_package='', allow_rules=None, - disallow_rules=None): - results = [] - - def callback(obj): - result = validate(obj) - - errors = [] - for errcode, errmsg in result.get('errors', []): - if allow_rules and errcode not in allow_rules: - continue - if disallow_rules and errcode in disallow_rules: - continue - errors.append((errcode, errmsg)) - - if len(errors): - result['errors'] = errors - results.append((obj, result)) - - with self._apply_patches(): - for symbol in self.symbols: - try: - obj = Docstring._load_obj(symbol) - except (ImportError, AttributeError): - print('{} is not available for import'.format(symbol)) - else: - self.traverse(callback, obj, from_package=from_package) - - return results diff --git a/dev/archery/archery/lang/rust.py b/dev/archery/archery/lang/rust.py deleted file mode 100644 index b1d765b7d52e..000000000000 --- a/dev/archery/archery/lang/rust.py +++ /dev/null @@ -1,23 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -from ..utils.command import Command, default_bin - - -class Cargo(Command): - def __init__(self, cargo_bin=None): - self.bin = default_bin(cargo_bin, "cargo") diff --git a/dev/archery/archery/release.py b/dev/archery/archery/release.py deleted file mode 100644 index acfe3fc23737..000000000000 --- a/dev/archery/archery/release.py +++ /dev/null @@ -1,535 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -from collections import defaultdict -import functools -import os -import re -import pathlib -import shelve -import warnings - -from git import Repo -from jira import JIRA -from semver import VersionInfo as SemVer - -from .utils.source import ArrowSources -from .utils.report import JinjaReport - - -def cached_property(fn): - return property(functools.lru_cache(maxsize=1)(fn)) - - -class Version(SemVer): - - __slots__ = ('released', 'release_date') - - def __init__(self, released=False, release_date=None, **kwargs): - super().__init__(**kwargs) - self.released = released - self.release_date = release_date - - @classmethod - def parse(cls, version, **kwargs): - return cls(**SemVer.parse(version).to_dict(), **kwargs) - - @classmethod - def from_jira(cls, jira_version): - return cls.parse( - jira_version.name, - released=jira_version.released, - release_date=getattr(jira_version, 'releaseDate', None) - ) - - -class Issue: - - def __init__(self, key, type, summary): - self.key = key - self.type = type - self.summary = summary - - @classmethod - def from_jira(cls, jira_issue): - return cls( - key=jira_issue.key, - type=jira_issue.fields.issuetype.name, - summary=jira_issue.fields.summary - ) - - @property - def project(self): - return self.key.split('-')[0] - - @property - def number(self): - return int(self.key.split('-')[1]) - - -class Jira(JIRA): - - def __init__(self, user=None, password=None, - url='https://issues.apache.org/jira'): - user = user or os.environ.get('APACHE_JIRA_USER') - password = password or os.environ.get('APACHE_JIRA_PASSWORD') - super().__init__(url, basic_auth=(user, password)) - - def project_version(self, version_string, project='ARROW'): - # query version from jira to populated with additional metadata - versions = {str(v): v for v in self.project_versions(project)} - return versions[version_string] - - def project_versions(self, project): - versions = [] - for v in super().project_versions(project): - try: - versions.append(Version.from_jira(v)) - except ValueError: - # ignore invalid semantic versions like JS-0.4.0 - continue - return sorted(versions, reverse=True) - - def issue(self, key): - return Issue.from_jira(super().issue(key)) - - def project_issues(self, version, project='ARROW'): - query = "project={} AND fixVersion={}".format(project, version) - issues = super().search_issues(query, maxResults=False) - return list(map(Issue.from_jira, issues)) - - -class CachedJira: - - def __init__(self, cache_path, jira=None): - self.jira = jira or Jira() - self.cache_path = cache_path - - def __getattr__(self, name): - attr = getattr(self.jira, name) - return self._cached(name, attr) if callable(attr) else attr - - def _cached(self, name, method): - def wrapper(*args, **kwargs): - key = str((name, args, kwargs)) - with shelve.open(self.cache_path) as cache: - try: - result = cache[key] - except KeyError: - cache[key] = result = method(*args, **kwargs) - return result - return wrapper - - -_TITLE_REGEX = re.compile( - r"(?P(?P(ARROW|PARQUET))\-\d+)?\s*:?\s*" - r"(?P\[.*\])?\s*(?P.*)" -) -_COMPONENT_REGEX = re.compile(r"\[([^\[\]]+)\]") - - -class CommitTitle: - - def __init__(self, summary, project=None, issue=None, components=None): - self.project = project - self.issue = issue - self.components = components or [] - self.summary = summary - - def __str__(self): - out = "" - if self.issue: - out += "{}: ".format(self.issue) - if self.components: - for component in self.components: - out += "[{}]".format(component) - out += " " - out += self.summary - return out - - def __eq__(self, other): - return ( - self.summary == other.summary and - self.project == other.project and - self.issue == other.issue and - self.components == other.components - ) - - def __hash__(self): - return hash( - (self.summary, self.project, self.issue, tuple(self.components)) - ) - - @classmethod - def parse(cls, headline): - matches = _TITLE_REGEX.match(headline) - if matches is None: - warnings.warn( - "Unable to parse commit message `{}`".format(headline) - ) - return CommitTitle(headline) - - values = matches.groupdict() - components = values.get('components') or '' - components = _COMPONENT_REGEX.findall(components) - - return CommitTitle( - values['summary'], - project=values.get('project'), - issue=values.get('issue'), - components=components - ) - - -class Commit: - - def __init__(self, wrapped): - self._title = CommitTitle.parse(wrapped.summary) - self._wrapped = wrapped - - def __getattr__(self, attr): - if hasattr(self._title, attr): - return getattr(self._title, attr) - else: - return getattr(self._wrapped, attr) - - def __repr__(self): - template = '' - return template.format(self.hexsha, self.issue, self.components, - self.summary) - - @property - def url(self): - return 'https://github.com/apache/arrow/commit/{}'.format(self.hexsha) - - @property - def title(self): - return self._title - - -class ReleaseCuration(JinjaReport): - templates = { - 'console': 'release_curation.txt.j2' - } - fields = [ - 'release', - 'within', - 'outside', - 'nojira', - 'parquet', - 'nopatch' - ] - - -class JiraChangelog(JinjaReport): - templates = { - 'markdown': 'release_changelog.md.j2', - 'html': 'release_changelog.html.j2' - } - fields = [ - 'release', - 'categories' - ] - - -class Release: - - def __init__(self): - raise TypeError("Do not initialize Release class directly, use " - "Release.from_jira(version) instead.") - - def __repr__(self): - if self.version.released: - status = "released_at={!r}".format(self.version.release_date) - else: - status = "pending" - return "<{} {!r} {}>".format(self.__class__.__name__, - str(self.version), status) - - @staticmethod - def from_jira(version, jira=None, repo=None): - if jira is None: - jira = Jira() - elif isinstance(jira, str): - jira = Jira(jira) - elif not isinstance(jira, (Jira, CachedJira)): - raise TypeError("`jira` argument must be a server url or a valid " - "Jira instance") - - if repo is None: - arrow = ArrowSources.find() - repo = Repo(arrow.path) - elif isinstance(repo, (str, pathlib.Path)): - repo = Repo(repo) - elif not isinstance(repo, Repo): - raise TypeError("`repo` argument must be a path or a valid Repo " - "instance") - - if isinstance(version, str): - version = jira.project_version(version, project='ARROW') - elif not isinstance(version, Version): - raise TypeError(version) - - # decide the type of the release based on the version number - if version.patch == 0: - if version.minor == 0: - klass = MajorRelease - elif version.major == 0: - # handle minor releases before 1.0 as major releases - klass = MajorRelease - else: - klass = MinorRelease - else: - klass = PatchRelease - - # prevent instantiating release object directly - obj = klass.__new__(klass) - obj.version = version - obj.jira = jira - obj.repo = repo - - return obj - - @property - def is_released(self): - return self.version.released - - @property - def tag(self): - return "apache-arrow-{}".format(str(self.version)) - - @property - def branch(self): - raise NotImplementedError() - - @property - def siblings(self): - """ - Releases to consider when calculating previous and next releases. - """ - raise NotImplementedError() - - @cached_property - def previous(self): - # select all non-patch releases - position = self.siblings.index(self.version) - try: - previous = self.siblings[position + 1] - except IndexError: - # first release doesn't have a previous one - return None - else: - return Release.from_jira(previous, jira=self.jira, repo=self.repo) - - @cached_property - def next(self): - # select all non-patch releases - position = self.siblings.index(self.version) - if position <= 0: - raise ValueError("There is no upcoming release set in JIRA after " - "version {}".format(self.version)) - upcoming = self.siblings[position - 1] - return Release.from_jira(upcoming, jira=self.jira, repo=self.repo) - - @cached_property - def issues(self): - issues = self.jira.project_issues(self.version, project='ARROW') - return {i.key: i for i in issues} - - @cached_property - def commits(self): - """ - All commits applied between two versions. - """ - if self.previous is None: - # first release - lower = '' - else: - lower = self.repo.tags[self.previous.tag] - - if self.version.released: - upper = self.repo.tags[self.tag] - else: - try: - upper = self.repo.branches[self.branch] - except IndexError: - warnings.warn("Release branch `{}` doesn't exist." - .format(self.branch)) - return [] - - commit_range = "{}..{}".format(lower, upper) - return list(map(Commit, self.repo.iter_commits(commit_range))) - - def curate(self): - # handle commits with parquet issue key specially and query them from - # jira and add it to the issues - release_issues = self.issues - - within, outside, nojira, parquet = [], [], [], [] - for c in self.commits: - if c.issue is None: - nojira.append(c) - elif c.issue in release_issues: - within.append((release_issues[c.issue], c)) - elif c.project == 'PARQUET': - parquet.append((self.jira.issue(c.issue), c)) - else: - outside.append((self.jira.issue(c.issue), c)) - - # remaining jira tickets - within_keys = {i.key for i, c in within} - nopatch = [issue for key, issue in release_issues.items() - if key not in within_keys] - - return ReleaseCuration(release=self, within=within, outside=outside, - nojira=nojira, parquet=parquet, nopatch=nopatch) - - def changelog(self): - release_issues = [] - - # get organized report for the release - curation = self.curate() - - # jira tickets having patches in the release - for issue, _ in curation.within: - release_issues.append(issue) - - # jira tickets without patches - for issue in curation.nopatch: - release_issues.append(issue) - - # parquet patches in the release - for issue, _ in curation.parquet: - release_issues.append(issue) - - # organize issues into categories - issue_types = { - 'Bug': 'Bug Fixes', - 'Improvement': 'New Features and Improvements', - 'New Feature': 'New Features and Improvements', - 'Sub-task': 'New Features and Improvements', - 'Task': 'New Features and Improvements', - 'Test': 'Bug Fixes', - 'Wish': 'New Features and Improvements', - } - categories = defaultdict(list) - for issue in release_issues: - categories[issue_types[issue.type]].append(issue) - - # sort issues by the issue key in ascending order - for name, issues in categories.items(): - issues.sort(key=lambda issue: (issue.project, issue.number)) - - return JiraChangelog(release=self, categories=categories) - - -class MaintenanceMixin: - """ - Utility methods for cherry-picking commits from the main branch. - """ - - def commits_to_pick(self, exclude_already_applied=True): - # collect commits applied on the main branch since the root of the - # maintenance branch (the previous major release) - if self.version.major == 0: - # treat minor releases as major releases preceeding 1.0.0 release - commit_range = "apache-arrow-0.{}.0..master".format( - self.version.minor - 1 - ) - else: - commit_range = "apache-arrow-{}.0.0..master".format( - self.version.major - ) - - # keeping the original order of the commits helps to minimize the merge - # conflicts during cherry-picks - commits = map(Commit, self.repo.iter_commits(commit_range)) - - # exclude patches that have been already applied to the maintenance - # branch, we cannot identify patches based on sha because it changes - # after the cherry pick so use commit title instead - if exclude_already_applied: - already_applied = {c.title for c in self.commits} - else: - already_applied = set() - - # iterate over the commits applied on the main branch and filter out - # the ones that are included in the jira release - patches_to_pick = [c for c in commits if - c.issue in self.issues and - c.title not in already_applied] - - return reversed(patches_to_pick) - - def cherry_pick_commits(self, recreate_branch=True): - if recreate_branch: - # delete, create and checkout the maintenance branch based off of - # the previous tag - if self.branch in self.repo.branches: - self.repo.git.branch('-D', self.branch) - self.repo.git.checkout(self.previous.tag, b=self.branch) - else: - # just checkout the already existing maintenance branch - self.repo.git.checkout(self.branch) - - # cherry pick the commits based on the jira tickets - for commit in self.commits_to_pick(): - self.repo.git.cherry_pick(commit.hexsha) - - -class MajorRelease(Release): - - @property - def branch(self): - return "master" - - @cached_property - def siblings(self): - """ - Filter only the major releases. - """ - # handle minor releases before 1.0 as major releases - return [v for v in self.jira.project_versions('ARROW') - if v.patch == 0 and (v.major == 0 or v.minor == 0)] - - -class MinorRelease(Release, MaintenanceMixin): - - @property - def branch(self): - return "maint-{}.x.x".format(self.version.major) - - @cached_property - def siblings(self): - """ - Filter the major and minor releases. - """ - return [v for v in self.jira.project_versions('ARROW') if v.patch == 0] - - -class PatchRelease(Release, MaintenanceMixin): - - @property - def branch(self): - return "maint-{}.{}.x".format(self.version.major, self.version.minor) - - @cached_property - def siblings(self): - """ - No filtering, consider all releases. - """ - return self.jira.project_versions('ARROW') diff --git a/dev/archery/archery/templates/release_changelog.md.j2 b/dev/archery/archery/templates/release_changelog.md.j2 deleted file mode 100644 index c0406ddf4e22..000000000000 --- a/dev/archery/archery/templates/release_changelog.md.j2 +++ /dev/null @@ -1,29 +0,0 @@ -{# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. -#} -# Apache Arrow {{ release.version }} ({{ release.version.release_date or today() }}) - -{% for category, issues in categories.items() -%} - -## {{ category }} - -{% for issue in issues -%} -* [{{ issue.key }}](https://issues.apache.org/jira/browse/{{ issue.key }}) - {{ issue.summary | md }} -{% endfor %} - -{% endfor %} diff --git a/dev/archery/archery/templates/release_curation.txt.j2 b/dev/archery/archery/templates/release_curation.txt.j2 deleted file mode 100644 index a5d11e9d4af5..000000000000 --- a/dev/archery/archery/templates/release_curation.txt.j2 +++ /dev/null @@ -1,41 +0,0 @@ -{# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. -#} -Total number of JIRA tickets assigned to version {{ release.version }}: {{ release.issues|length }} - -Total number of applied patches since version {{ release.previous.version }}: {{ release.commits|length }} - -Patches with assigned issue in version {{ release.version }}: -{% for issue, commit in within -%} - - {{ commit.url }} {{ commit.title }} -{% endfor %} - -Patches with assigned issue outside of version {{ release.version }}: -{% for issue, commit in outside -%} - - {{ commit.url }} {{ commit.title }} -{% endfor %} - -Patches in version {{ release.version }} without a linked issue: -{% for commit in nojira -%} - - {{ commit.url }} {{ commit.title }} -{% endfor %} - -JIRA issues in version {{ release.version }} without a linked patch: -{% for issue in nopatch -%} - - https://issues.apache.org/jira/browse/{{ issue.key }} -{% endfor %} diff --git a/dev/archery/archery/testing.py b/dev/archery/archery/testing.py deleted file mode 100644 index 471a54d4c72c..000000000000 --- a/dev/archery/archery/testing.py +++ /dev/null @@ -1,83 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -from contextlib import contextmanager -import os -from unittest import mock -import re - - -class DotDict(dict): - - def __getattr__(self, key): - try: - item = self[key] - except KeyError: - raise AttributeError(key) - if isinstance(item, dict): - return DotDict(item) - else: - return item - - -class PartialEnv(dict): - - def __eq__(self, other): - return self.items() <= other.items() - - -_mock_call_type = type(mock.call()) - - -def _ensure_mock_call_object(obj, **kwargs): - if isinstance(obj, _mock_call_type): - return obj - elif isinstance(obj, str): - cmd = re.split(r"\s+", obj) - return mock.call(cmd, **kwargs) - elif isinstance(obj, list): - return mock.call(obj, **kwargs) - else: - raise TypeError(obj) - - -class SuccessfulSubprocessResult: - - def check_returncode(self): - return - - -@contextmanager -def assert_subprocess_calls(expected_commands_or_calls, **kwargs): - calls = [ - _ensure_mock_call_object(obj, **kwargs) - for obj in expected_commands_or_calls - ] - with mock.patch('subprocess.run', autospec=True) as run: - run.return_value = SuccessfulSubprocessResult() - yield run - run.assert_has_calls(calls) - - -@contextmanager -def override_env(mapping): - original = os.environ - try: - os.environ = dict(os.environ, **mapping) - yield os.environ - finally: - os.environ = original diff --git a/dev/archery/archery/tests/fixtures/archery-benchmark-diff-empty-lines.jsonl b/dev/archery/archery/tests/fixtures/archery-benchmark-diff-empty-lines.jsonl deleted file mode 100644 index 5854eb75c979..000000000000 --- a/dev/archery/archery/tests/fixtures/archery-benchmark-diff-empty-lines.jsonl +++ /dev/null @@ -1,6 +0,0 @@ -{"benchmark": "RegressionSumKernel/32768/10", "change": 0.0046756468886368545, "regression": false, "baseline": 13265442258.099466, "contender": 13327466781.91994, "unit": "bytes_per_second", "less_is_better": false, "suite": "arrow-compute-aggregate-benchmark"} -{"benchmark": "RegressionSumKernel/32768/1", "change": 0.0025108399115900733, "regression": false, "baseline": 15181891659.539782, "contender": 15220010959.05199, "unit": "bytes_per_second", "less_is_better": false, "suite": "arrow-compute-aggregate-benchmark"} - -{"benchmark": "RegressionSumKernel/32768/50", "change": 0.00346735806287155, "regression": false, "baseline": 11471825667.817123, "contender": 11511602595.042286, "unit": "bytes_per_second", "less_is_better": false, "suite": "arrow-compute-aggregate-benchmark"} - -{"benchmark": "RegressionSumKernel/32768/0", "change": 0.010140954727954987, "regression": false, "baseline": 18316987019.994465, "contender": 18502738756.116768, "unit": "bytes_per_second", "less_is_better": false, "suite": "arrow-compute-aggregate-benchmark"} diff --git a/dev/archery/archery/tests/fixtures/archery-benchmark-diff.jsonl b/dev/archery/archery/tests/fixtures/archery-benchmark-diff.jsonl deleted file mode 100644 index 1e25810d776e..000000000000 --- a/dev/archery/archery/tests/fixtures/archery-benchmark-diff.jsonl +++ /dev/null @@ -1,4 +0,0 @@ -{"benchmark":"RegressionSumKernel/32768/50","change":-0.001550846227215492,"regression":false,"baseline":19241207435.428757,"contender":19211367281.47045,"unit":"bytes_per_second","less_is_better":false,"suite":"arrow-compute-aggregate-benchmark"} -{"benchmark":"RegressionSumKernel/32768/1","change":0.0020681767923465765,"regression":true,"baseline":24823170673.777943,"contender":24771831968.277977,"unit":"bytes_per_second","less_is_better":false,"suite":"arrow-compute-aggregate-benchmark"} -{"benchmark":"RegressionSumKernel/32768/10","change":0.0033323376378746905,"regression":false,"baseline":21902707565.968014,"contender":21975694782.76145,"unit":"bytes_per_second","less_is_better":false,"suite":"arrow-compute-aggregate-benchmark"} -{"benchmark":"RegressionSumKernel/32768/0","change":-0.004918126090954414,"regression":true,"baseline":27685006611.446762,"contender":27821164964.790764,"unit":"bytes_per_second","less_is_better":false,"suite":"arrow-compute-aggregate-benchmark"} diff --git a/dev/archery/archery/tests/fixtures/event-issue-comment-build-command.json b/dev/archery/archery/tests/fixtures/event-issue-comment-build-command.json deleted file mode 100644 index d591105f0798..000000000000 --- a/dev/archery/archery/tests/fixtures/event-issue-comment-build-command.json +++ /dev/null @@ -1,212 +0,0 @@ -{ - "action": "created", - "comment": { - "author_association": "MEMBER", - "body": "@ursabot build", - "created_at": "2019-04-05T11:55:43Z", - "html_url": "https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480248726", - "id": 480248726, - "issue_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26", - "node_id": "MDEyOklzc3VlQ29tbWVudDQ4MDI0ODcyNg==", - "updated_at": "2019-04-05T11:55:43Z", - "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480248726", - "user": { - "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4", - "events_url": "https://api.github.com/users/kszucs/events{/privacy}", - "followers_url": "https://api.github.com/users/kszucs/followers", - "following_url": "https://api.github.com/users/kszucs/following{/other_user}", - "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}", - "gravatar_id": "", - "html_url": "https://github.com/kszucs", - "id": 961747, - "login": "kszucs", - "node_id": "MDQ6VXNlcjk2MTc0Nw==", - "organizations_url": "https://api.github.com/users/kszucs/orgs", - "received_events_url": "https://api.github.com/users/kszucs/received_events", - "repos_url": "https://api.github.com/users/kszucs/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions", - "type": "User", - "url": "https://api.github.com/users/kszucs" - } - }, - "issue": { - "assignee": null, - "assignees": [], - "author_association": "MEMBER", - "body": "", - "closed_at": null, - "comments": 3, - "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments", - "created_at": "2019-04-05T11:22:15Z", - "events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/events", - "html_url": "https://github.com/ursa-labs/ursabot/pull/26", - "id": 429706959, - "labels": [], - "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}", - "locked": false, - "milestone": null, - "node_id": "MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy", - "number": 26, - "pull_request": { - "diff_url": "https://github.com/ursa-labs/ursabot/pull/26.diff", - "html_url": "https://github.com/ursa-labs/ursabot/pull/26", - "patch_url": "https://github.com/ursa-labs/ursabot/pull/26.patch", - "url": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26" - }, - "repository_url": "https://api.github.com/repos/ursa-labs/ursabot", - "state": "open", - "title": "Unittests for GithubHook", - "updated_at": "2019-04-05T11:55:43Z", - "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26", - "user": { - "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4", - "events_url": "https://api.github.com/users/kszucs/events{/privacy}", - "followers_url": "https://api.github.com/users/kszucs/followers", - "following_url": "https://api.github.com/users/kszucs/following{/other_user}", - "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}", - "gravatar_id": "", - "html_url": "https://github.com/kszucs", - "id": 961747, - "login": "kszucs", - "node_id": "MDQ6VXNlcjk2MTc0Nw==", - "organizations_url": "https://api.github.com/users/kszucs/orgs", - "received_events_url": "https://api.github.com/users/kszucs/received_events", - "repos_url": "https://api.github.com/users/kszucs/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions", - "type": "User", - "url": "https://api.github.com/users/kszucs" - } - }, - "organization": { - "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4", - "description": "Innovation lab for open source data science tools, powered by Apache Arrow", - "events_url": "https://api.github.com/orgs/ursa-labs/events", - "hooks_url": "https://api.github.com/orgs/ursa-labs/hooks", - "id": 46514972, - "issues_url": "https://api.github.com/orgs/ursa-labs/issues", - "login": "ursa-labs", - "members_url": "https://api.github.com/orgs/ursa-labs/members{/member}", - "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy", - "public_members_url": "https://api.github.com/orgs/ursa-labs/public_members{/member}", - "repos_url": "https://api.github.com/orgs/ursa-labs/repos", - "url": "https://api.github.com/orgs/ursa-labs" - }, - "repository": { - "archive_url": "https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}", - "archived": false, - "assignees_url": "https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}", - "blobs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}", - "branches_url": "https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}", - "clone_url": "https://github.com/ursa-labs/ursabot.git", - "collaborators_url": "https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}", - "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/comments{/number}", - "commits_url": "https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}", - "compare_url": "https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}", - "contributors_url": "https://api.github.com/repos/ursa-labs/ursabot/contributors", - "created_at": "2019-02-04T15:40:31Z", - "default_branch": "master", - "deployments_url": "https://api.github.com/repos/ursa-labs/ursabot/deployments", - "description": null, - "disabled": false, - "downloads_url": "https://api.github.com/repos/ursa-labs/ursabot/downloads", - "events_url": "https://api.github.com/repos/ursa-labs/ursabot/events", - "fork": false, - "forks": 0, - "forks_count": 0, - "forks_url": "https://api.github.com/repos/ursa-labs/ursabot/forks", - "full_name": "ursa-labs/ursabot", - "git_commits_url": "https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}", - "git_refs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}", - "git_tags_url": "https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}", - "git_url": "git://github.com/ursa-labs/ursabot.git", - "has_downloads": true, - "has_issues": true, - "has_pages": false, - "has_projects": true, - "has_wiki": true, - "homepage": null, - "hooks_url": "https://api.github.com/repos/ursa-labs/ursabot/hooks", - "html_url": "https://github.com/ursa-labs/ursabot", - "id": 169101701, - "issue_comment_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}", - "issue_events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}", - "issues_url": "https://api.github.com/repos/ursa-labs/ursabot/issues{/number}", - "keys_url": "https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}", - "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/labels{/name}", - "language": "Jupyter Notebook", - "languages_url": "https://api.github.com/repos/ursa-labs/ursabot/languages", - "license": null, - "merges_url": "https://api.github.com/repos/ursa-labs/ursabot/merges", - "milestones_url": "https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}", - "mirror_url": null, - "name": "ursabot", - "node_id": "MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=", - "notifications_url": "https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}", - "open_issues": 19, - "open_issues_count": 19, - "owner": { - "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4", - "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}", - "followers_url": "https://api.github.com/users/ursa-labs/followers", - "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}", - "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}", - "gravatar_id": "", - "html_url": "https://github.com/ursa-labs", - "id": 46514972, - "login": "ursa-labs", - "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy", - "organizations_url": "https://api.github.com/users/ursa-labs/orgs", - "received_events_url": "https://api.github.com/users/ursa-labs/received_events", - "repos_url": "https://api.github.com/users/ursa-labs/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions", - "type": "Organization", - "url": "https://api.github.com/users/ursa-labs" - }, - "private": false, - "pulls_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}", - "pushed_at": "2019-04-05T11:22:16Z", - "releases_url": "https://api.github.com/repos/ursa-labs/ursabot/releases{/id}", - "size": 892, - "ssh_url": "git@github.com:ursa-labs/ursabot.git", - "stargazers_count": 1, - "stargazers_url": "https://api.github.com/repos/ursa-labs/ursabot/stargazers", - "statuses_url": "https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}", - "subscribers_url": "https://api.github.com/repos/ursa-labs/ursabot/subscribers", - "subscription_url": "https://api.github.com/repos/ursa-labs/ursabot/subscription", - "svn_url": "https://github.com/ursa-labs/ursabot", - "tags_url": "https://api.github.com/repos/ursa-labs/ursabot/tags", - "teams_url": "https://api.github.com/repos/ursa-labs/ursabot/teams", - "trees_url": "https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}", - "updated_at": "2019-04-04T17:49:10Z", - "url": "https://api.github.com/repos/ursa-labs/ursabot", - "watchers": 1, - "watchers_count": 1 - }, - "sender": { - "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4", - "events_url": "https://api.github.com/users/kszucs/events{/privacy}", - "followers_url": "https://api.github.com/users/kszucs/followers", - "following_url": "https://api.github.com/users/kszucs/following{/other_user}", - "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}", - "gravatar_id": "", - "html_url": "https://github.com/kszucs", - "id": 961747, - "login": "kszucs", - "node_id": "MDQ6VXNlcjk2MTc0Nw==", - "organizations_url": "https://api.github.com/users/kszucs/orgs", - "received_events_url": "https://api.github.com/users/kszucs/received_events", - "repos_url": "https://api.github.com/users/kszucs/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions", - "type": "User", - "url": "https://api.github.com/users/kszucs" - } -} \ No newline at end of file diff --git a/dev/archery/archery/tests/fixtures/event-issue-comment-by-non-authorized-user.json b/dev/archery/archery/tests/fixtures/event-issue-comment-by-non-authorized-user.json deleted file mode 100644 index 5a8f3461c0ca..000000000000 --- a/dev/archery/archery/tests/fixtures/event-issue-comment-by-non-authorized-user.json +++ /dev/null @@ -1,212 +0,0 @@ -{ - "action": "created", - "comment": { - "author_association": "NONE", - "body": "Unknown command \"\"", - "created_at": "2019-04-05T11:35:47Z", - "html_url": "https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480243815", - "id": 480243815, - "issue_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26", - "node_id": "MDEyOklzc3VlQ29tbWVudDQ4MDI0MzgxNQ==", - "updated_at": "2019-04-05T11:35:47Z", - "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480243815", - "user": { - "avatar_url": "https://avatars2.githubusercontent.com/u/49275095?v=4", - "events_url": "https://api.github.com/users/ursabot/events{/privacy}", - "followers_url": "https://api.github.com/users/ursabot/followers", - "following_url": "https://api.github.com/users/ursabot/following{/other_user}", - "gists_url": "https://api.github.com/users/ursabot/gists{/gist_id}", - "gravatar_id": "", - "html_url": "https://github.com/ursabot", - "id": 49275095, - "login": "someone", - "node_id": "MDQ6VXNlcjQ5Mjc1MDk1", - "organizations_url": "https://api.github.com/users/ursabot/orgs", - "received_events_url": "https://api.github.com/users/ursabot/received_events", - "repos_url": "https://api.github.com/users/ursabot/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/ursabot/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/ursabot/subscriptions", - "type": "User", - "url": "https://api.github.com/users/ursabot" - } - }, - "issue": { - "assignee": null, - "assignees": [], - "author_association": "NONE", - "body": "", - "closed_at": null, - "comments": 2, - "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments", - "created_at": "2019-04-05T11:22:15Z", - "events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/events", - "html_url": "https://github.com/ursa-labs/ursabot/pull/26", - "id": 429706959, - "labels": [], - "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}", - "locked": false, - "milestone": null, - "node_id": "MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy", - "number": 26, - "pull_request": { - "diff_url": "https://github.com/ursa-labs/ursabot/pull/26.diff", - "html_url": "https://github.com/ursa-labs/ursabot/pull/26", - "patch_url": "https://github.com/ursa-labs/ursabot/pull/26.patch", - "url": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26" - }, - "repository_url": "https://api.github.com/repos/ursa-labs/ursabot", - "state": "open", - "title": "Unittests for GithubHook", - "updated_at": "2019-04-05T11:35:47Z", - "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26", - "user": { - "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4", - "events_url": "https://api.github.com/users/kszucs/events{/privacy}", - "followers_url": "https://api.github.com/users/kszucs/followers", - "following_url": "https://api.github.com/users/kszucs/following{/other_user}", - "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}", - "gravatar_id": "", - "html_url": "https://github.com/kszucs", - "id": 961747, - "login": "kszucs", - "node_id": "MDQ6VXNlcjk2MTc0Nw==", - "organizations_url": "https://api.github.com/users/kszucs/orgs", - "received_events_url": "https://api.github.com/users/kszucs/received_events", - "repos_url": "https://api.github.com/users/kszucs/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions", - "type": "User", - "url": "https://api.github.com/users/kszucs" - } - }, - "organization": { - "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4", - "description": "Innovation lab for open source data science tools, powered by Apache Arrow", - "events_url": "https://api.github.com/orgs/ursa-labs/events", - "hooks_url": "https://api.github.com/orgs/ursa-labs/hooks", - "id": 46514972, - "issues_url": "https://api.github.com/orgs/ursa-labs/issues", - "login": "ursa-labs", - "members_url": "https://api.github.com/orgs/ursa-labs/members{/member}", - "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy", - "public_members_url": "https://api.github.com/orgs/ursa-labs/public_members{/member}", - "repos_url": "https://api.github.com/orgs/ursa-labs/repos", - "url": "https://api.github.com/orgs/ursa-labs" - }, - "repository": { - "archive_url": "https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}", - "archived": false, - "assignees_url": "https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}", - "blobs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}", - "branches_url": "https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}", - "clone_url": "https://github.com/ursa-labs/ursabot.git", - "collaborators_url": "https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}", - "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/comments{/number}", - "commits_url": "https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}", - "compare_url": "https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}", - "contributors_url": "https://api.github.com/repos/ursa-labs/ursabot/contributors", - "created_at": "2019-02-04T15:40:31Z", - "default_branch": "master", - "deployments_url": "https://api.github.com/repos/ursa-labs/ursabot/deployments", - "description": null, - "disabled": false, - "downloads_url": "https://api.github.com/repos/ursa-labs/ursabot/downloads", - "events_url": "https://api.github.com/repos/ursa-labs/ursabot/events", - "fork": false, - "forks": 0, - "forks_count": 0, - "forks_url": "https://api.github.com/repos/ursa-labs/ursabot/forks", - "full_name": "ursa-labs/ursabot", - "git_commits_url": "https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}", - "git_refs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}", - "git_tags_url": "https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}", - "git_url": "git://github.com/ursa-labs/ursabot.git", - "has_downloads": true, - "has_issues": true, - "has_pages": false, - "has_projects": true, - "has_wiki": true, - "homepage": null, - "hooks_url": "https://api.github.com/repos/ursa-labs/ursabot/hooks", - "html_url": "https://github.com/ursa-labs/ursabot", - "id": 169101701, - "issue_comment_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}", - "issue_events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}", - "issues_url": "https://api.github.com/repos/ursa-labs/ursabot/issues{/number}", - "keys_url": "https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}", - "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/labels{/name}", - "language": "Jupyter Notebook", - "languages_url": "https://api.github.com/repos/ursa-labs/ursabot/languages", - "license": null, - "merges_url": "https://api.github.com/repos/ursa-labs/ursabot/merges", - "milestones_url": "https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}", - "mirror_url": null, - "name": "someone", - "node_id": "MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=", - "notifications_url": "https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}", - "open_issues": 19, - "open_issues_count": 19, - "owner": { - "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4", - "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}", - "followers_url": "https://api.github.com/users/ursa-labs/followers", - "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}", - "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}", - "gravatar_id": "", - "html_url": "https://github.com/ursa-labs", - "id": 46514972, - "login": "ursa-labs", - "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy", - "organizations_url": "https://api.github.com/users/ursa-labs/orgs", - "received_events_url": "https://api.github.com/users/ursa-labs/received_events", - "repos_url": "https://api.github.com/users/ursa-labs/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions", - "type": "Organization", - "url": "https://api.github.com/users/ursa-labs" - }, - "private": false, - "pulls_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}", - "pushed_at": "2019-04-05T11:22:16Z", - "releases_url": "https://api.github.com/repos/ursa-labs/ursabot/releases{/id}", - "size": 892, - "ssh_url": "git@github.com:ursa-labs/ursabot.git", - "stargazers_count": 1, - "stargazers_url": "https://api.github.com/repos/ursa-labs/ursabot/stargazers", - "statuses_url": "https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}", - "subscribers_url": "https://api.github.com/repos/ursa-labs/ursabot/subscribers", - "subscription_url": "https://api.github.com/repos/ursa-labs/ursabot/subscription", - "svn_url": "https://github.com/ursa-labs/ursabot", - "tags_url": "https://api.github.com/repos/ursa-labs/ursabot/tags", - "teams_url": "https://api.github.com/repos/ursa-labs/ursabot/teams", - "trees_url": "https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}", - "updated_at": "2019-04-04T17:49:10Z", - "url": "https://api.github.com/repos/ursa-labs/ursabot", - "watchers": 1, - "watchers_count": 1 - }, - "sender": { - "avatar_url": "https://avatars2.githubusercontent.com/u/49275095?v=4", - "events_url": "https://api.github.com/users/ursabot/events{/privacy}", - "followers_url": "https://api.github.com/users/ursabot/followers", - "following_url": "https://api.github.com/users/ursabot/following{/other_user}", - "gists_url": "https://api.github.com/users/ursabot/gists{/gist_id}", - "gravatar_id": "", - "html_url": "https://github.com/ursabot", - "id": 49275095, - "login": "someone", - "node_id": "MDQ6VXNlcjQ5Mjc1MDk1", - "organizations_url": "https://api.github.com/users/ursabot/orgs", - "received_events_url": "https://api.github.com/users/ursabot/received_events", - "repos_url": "https://api.github.com/users/ursabot/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/ursabot/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/ursabot/subscriptions", - "type": "User", - "url": "https://api.github.com/users/ursabot" - } -} \ No newline at end of file diff --git a/dev/archery/archery/tests/fixtures/event-issue-comment-by-ursabot.json b/dev/archery/archery/tests/fixtures/event-issue-comment-by-ursabot.json deleted file mode 100644 index bfb7210df8a3..000000000000 --- a/dev/archery/archery/tests/fixtures/event-issue-comment-by-ursabot.json +++ /dev/null @@ -1,212 +0,0 @@ -{ - "action": "created", - "comment": { - "author_association": "NONE", - "body": "Unknown command \"\"", - "created_at": "2019-04-05T11:35:47Z", - "html_url": "https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480243815", - "id": 480243815, - "issue_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26", - "node_id": "MDEyOklzc3VlQ29tbWVudDQ4MDI0MzgxNQ==", - "updated_at": "2019-04-05T11:35:47Z", - "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480243815", - "user": { - "avatar_url": "https://avatars2.githubusercontent.com/u/49275095?v=4", - "events_url": "https://api.github.com/users/ursabot/events{/privacy}", - "followers_url": "https://api.github.com/users/ursabot/followers", - "following_url": "https://api.github.com/users/ursabot/following{/other_user}", - "gists_url": "https://api.github.com/users/ursabot/gists{/gist_id}", - "gravatar_id": "", - "html_url": "https://github.com/ursabot", - "id": 49275095, - "login": "ursabot", - "node_id": "MDQ6VXNlcjQ5Mjc1MDk1", - "organizations_url": "https://api.github.com/users/ursabot/orgs", - "received_events_url": "https://api.github.com/users/ursabot/received_events", - "repos_url": "https://api.github.com/users/ursabot/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/ursabot/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/ursabot/subscriptions", - "type": "User", - "url": "https://api.github.com/users/ursabot" - } - }, - "issue": { - "assignee": null, - "assignees": [], - "author_association": "MEMBER", - "body": "", - "closed_at": null, - "comments": 2, - "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments", - "created_at": "2019-04-05T11:22:15Z", - "events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/events", - "html_url": "https://github.com/ursa-labs/ursabot/pull/26", - "id": 429706959, - "labels": [], - "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}", - "locked": false, - "milestone": null, - "node_id": "MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy", - "number": 26, - "pull_request": { - "diff_url": "https://github.com/ursa-labs/ursabot/pull/26.diff", - "html_url": "https://github.com/ursa-labs/ursabot/pull/26", - "patch_url": "https://github.com/ursa-labs/ursabot/pull/26.patch", - "url": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26" - }, - "repository_url": "https://api.github.com/repos/ursa-labs/ursabot", - "state": "open", - "title": "Unittests for GithubHook", - "updated_at": "2019-04-05T11:35:47Z", - "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26", - "user": { - "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4", - "events_url": "https://api.github.com/users/kszucs/events{/privacy}", - "followers_url": "https://api.github.com/users/kszucs/followers", - "following_url": "https://api.github.com/users/kszucs/following{/other_user}", - "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}", - "gravatar_id": "", - "html_url": "https://github.com/kszucs", - "id": 961747, - "login": "kszucs", - "node_id": "MDQ6VXNlcjk2MTc0Nw==", - "organizations_url": "https://api.github.com/users/kszucs/orgs", - "received_events_url": "https://api.github.com/users/kszucs/received_events", - "repos_url": "https://api.github.com/users/kszucs/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions", - "type": "User", - "url": "https://api.github.com/users/kszucs" - } - }, - "organization": { - "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4", - "description": "Innovation lab for open source data science tools, powered by Apache Arrow", - "events_url": "https://api.github.com/orgs/ursa-labs/events", - "hooks_url": "https://api.github.com/orgs/ursa-labs/hooks", - "id": 46514972, - "issues_url": "https://api.github.com/orgs/ursa-labs/issues", - "login": "ursa-labs", - "members_url": "https://api.github.com/orgs/ursa-labs/members{/member}", - "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy", - "public_members_url": "https://api.github.com/orgs/ursa-labs/public_members{/member}", - "repos_url": "https://api.github.com/orgs/ursa-labs/repos", - "url": "https://api.github.com/orgs/ursa-labs" - }, - "repository": { - "archive_url": "https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}", - "archived": false, - "assignees_url": "https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}", - "blobs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}", - "branches_url": "https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}", - "clone_url": "https://github.com/ursa-labs/ursabot.git", - "collaborators_url": "https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}", - "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/comments{/number}", - "commits_url": "https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}", - "compare_url": "https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}", - "contributors_url": "https://api.github.com/repos/ursa-labs/ursabot/contributors", - "created_at": "2019-02-04T15:40:31Z", - "default_branch": "master", - "deployments_url": "https://api.github.com/repos/ursa-labs/ursabot/deployments", - "description": null, - "disabled": false, - "downloads_url": "https://api.github.com/repos/ursa-labs/ursabot/downloads", - "events_url": "https://api.github.com/repos/ursa-labs/ursabot/events", - "fork": false, - "forks": 0, - "forks_count": 0, - "forks_url": "https://api.github.com/repos/ursa-labs/ursabot/forks", - "full_name": "ursa-labs/ursabot", - "git_commits_url": "https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}", - "git_refs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}", - "git_tags_url": "https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}", - "git_url": "git://github.com/ursa-labs/ursabot.git", - "has_downloads": true, - "has_issues": true, - "has_pages": false, - "has_projects": true, - "has_wiki": true, - "homepage": null, - "hooks_url": "https://api.github.com/repos/ursa-labs/ursabot/hooks", - "html_url": "https://github.com/ursa-labs/ursabot", - "id": 169101701, - "issue_comment_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}", - "issue_events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}", - "issues_url": "https://api.github.com/repos/ursa-labs/ursabot/issues{/number}", - "keys_url": "https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}", - "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/labels{/name}", - "language": "Jupyter Notebook", - "languages_url": "https://api.github.com/repos/ursa-labs/ursabot/languages", - "license": null, - "merges_url": "https://api.github.com/repos/ursa-labs/ursabot/merges", - "milestones_url": "https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}", - "mirror_url": null, - "name": "ursabot", - "node_id": "MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=", - "notifications_url": "https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}", - "open_issues": 19, - "open_issues_count": 19, - "owner": { - "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4", - "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}", - "followers_url": "https://api.github.com/users/ursa-labs/followers", - "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}", - "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}", - "gravatar_id": "", - "html_url": "https://github.com/ursa-labs", - "id": 46514972, - "login": "ursa-labs", - "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy", - "organizations_url": "https://api.github.com/users/ursa-labs/orgs", - "received_events_url": "https://api.github.com/users/ursa-labs/received_events", - "repos_url": "https://api.github.com/users/ursa-labs/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions", - "type": "Organization", - "url": "https://api.github.com/users/ursa-labs" - }, - "private": false, - "pulls_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}", - "pushed_at": "2019-04-05T11:22:16Z", - "releases_url": "https://api.github.com/repos/ursa-labs/ursabot/releases{/id}", - "size": 892, - "ssh_url": "git@github.com:ursa-labs/ursabot.git", - "stargazers_count": 1, - "stargazers_url": "https://api.github.com/repos/ursa-labs/ursabot/stargazers", - "statuses_url": "https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}", - "subscribers_url": "https://api.github.com/repos/ursa-labs/ursabot/subscribers", - "subscription_url": "https://api.github.com/repos/ursa-labs/ursabot/subscription", - "svn_url": "https://github.com/ursa-labs/ursabot", - "tags_url": "https://api.github.com/repos/ursa-labs/ursabot/tags", - "teams_url": "https://api.github.com/repos/ursa-labs/ursabot/teams", - "trees_url": "https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}", - "updated_at": "2019-04-04T17:49:10Z", - "url": "https://api.github.com/repos/ursa-labs/ursabot", - "watchers": 1, - "watchers_count": 1 - }, - "sender": { - "avatar_url": "https://avatars2.githubusercontent.com/u/49275095?v=4", - "events_url": "https://api.github.com/users/ursabot/events{/privacy}", - "followers_url": "https://api.github.com/users/ursabot/followers", - "following_url": "https://api.github.com/users/ursabot/following{/other_user}", - "gists_url": "https://api.github.com/users/ursabot/gists{/gist_id}", - "gravatar_id": "", - "html_url": "https://github.com/ursabot", - "id": 49275095, - "login": "ursabot", - "node_id": "MDQ6VXNlcjQ5Mjc1MDk1", - "organizations_url": "https://api.github.com/users/ursabot/orgs", - "received_events_url": "https://api.github.com/users/ursabot/received_events", - "repos_url": "https://api.github.com/users/ursabot/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/ursabot/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/ursabot/subscriptions", - "type": "User", - "url": "https://api.github.com/users/ursabot" - } -} \ No newline at end of file diff --git a/dev/archery/archery/tests/fixtures/event-issue-comment-not-mentioning-ursabot.json b/dev/archery/archery/tests/fixtures/event-issue-comment-not-mentioning-ursabot.json deleted file mode 100644 index a3d450078aeb..000000000000 --- a/dev/archery/archery/tests/fixtures/event-issue-comment-not-mentioning-ursabot.json +++ /dev/null @@ -1,212 +0,0 @@ -{ - "action": "created", - "comment": { - "author_association": "MEMBER", - "body": "bear is no game", - "created_at": "2019-04-05T11:26:56Z", - "html_url": "https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480241727", - "id": 480241727, - "issue_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26", - "node_id": "MDEyOklzc3VlQ29tbWVudDQ4MDI0MTcyNw==", - "updated_at": "2019-04-05T11:26:56Z", - "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480241727", - "user": { - "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4", - "events_url": "https://api.github.com/users/kszucs/events{/privacy}", - "followers_url": "https://api.github.com/users/kszucs/followers", - "following_url": "https://api.github.com/users/kszucs/following{/other_user}", - "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}", - "gravatar_id": "", - "html_url": "https://github.com/kszucs", - "id": 961747, - "login": "kszucs", - "node_id": "MDQ6VXNlcjk2MTc0Nw==", - "organizations_url": "https://api.github.com/users/kszucs/orgs", - "received_events_url": "https://api.github.com/users/kszucs/received_events", - "repos_url": "https://api.github.com/users/kszucs/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions", - "type": "User", - "url": "https://api.github.com/users/kszucs" - } - }, - "issue": { - "assignee": null, - "assignees": [], - "author_association": "MEMBER", - "body": "", - "closed_at": null, - "comments": 0, - "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments", - "created_at": "2019-04-05T11:22:15Z", - "events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/events", - "html_url": "https://github.com/ursa-labs/ursabot/pull/26", - "id": 429706959, - "labels": [], - "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}", - "locked": false, - "milestone": null, - "node_id": "MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy", - "number": 26, - "pull_request": { - "diff_url": "https://github.com/ursa-labs/ursabot/pull/26.diff", - "html_url": "https://github.com/ursa-labs/ursabot/pull/26", - "patch_url": "https://github.com/ursa-labs/ursabot/pull/26.patch", - "url": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26" - }, - "repository_url": "https://api.github.com/repos/ursa-labs/ursabot", - "state": "open", - "title": "Unittests for GithubHook", - "updated_at": "2019-04-05T11:26:56Z", - "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26", - "user": { - "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4", - "events_url": "https://api.github.com/users/kszucs/events{/privacy}", - "followers_url": "https://api.github.com/users/kszucs/followers", - "following_url": "https://api.github.com/users/kszucs/following{/other_user}", - "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}", - "gravatar_id": "", - "html_url": "https://github.com/kszucs", - "id": 961747, - "login": "kszucs", - "node_id": "MDQ6VXNlcjk2MTc0Nw==", - "organizations_url": "https://api.github.com/users/kszucs/orgs", - "received_events_url": "https://api.github.com/users/kszucs/received_events", - "repos_url": "https://api.github.com/users/kszucs/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions", - "type": "User", - "url": "https://api.github.com/users/kszucs" - } - }, - "organization": { - "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4", - "description": "Innovation lab for open source data science tools, powered by Apache Arrow", - "events_url": "https://api.github.com/orgs/ursa-labs/events", - "hooks_url": "https://api.github.com/orgs/ursa-labs/hooks", - "id": 46514972, - "issues_url": "https://api.github.com/orgs/ursa-labs/issues", - "login": "ursa-labs", - "members_url": "https://api.github.com/orgs/ursa-labs/members{/member}", - "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy", - "public_members_url": "https://api.github.com/orgs/ursa-labs/public_members{/member}", - "repos_url": "https://api.github.com/orgs/ursa-labs/repos", - "url": "https://api.github.com/orgs/ursa-labs" - }, - "repository": { - "archive_url": "https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}", - "archived": false, - "assignees_url": "https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}", - "blobs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}", - "branches_url": "https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}", - "clone_url": "https://github.com/ursa-labs/ursabot.git", - "collaborators_url": "https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}", - "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/comments{/number}", - "commits_url": "https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}", - "compare_url": "https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}", - "contributors_url": "https://api.github.com/repos/ursa-labs/ursabot/contributors", - "created_at": "2019-02-04T15:40:31Z", - "default_branch": "master", - "deployments_url": "https://api.github.com/repos/ursa-labs/ursabot/deployments", - "description": null, - "disabled": false, - "downloads_url": "https://api.github.com/repos/ursa-labs/ursabot/downloads", - "events_url": "https://api.github.com/repos/ursa-labs/ursabot/events", - "fork": false, - "forks": 0, - "forks_count": 0, - "forks_url": "https://api.github.com/repos/ursa-labs/ursabot/forks", - "full_name": "ursa-labs/ursabot", - "git_commits_url": "https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}", - "git_refs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}", - "git_tags_url": "https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}", - "git_url": "git://github.com/ursa-labs/ursabot.git", - "has_downloads": true, - "has_issues": true, - "has_pages": false, - "has_projects": true, - "has_wiki": true, - "homepage": null, - "hooks_url": "https://api.github.com/repos/ursa-labs/ursabot/hooks", - "html_url": "https://github.com/ursa-labs/ursabot", - "id": 169101701, - "issue_comment_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}", - "issue_events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}", - "issues_url": "https://api.github.com/repos/ursa-labs/ursabot/issues{/number}", - "keys_url": "https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}", - "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/labels{/name}", - "language": "Jupyter Notebook", - "languages_url": "https://api.github.com/repos/ursa-labs/ursabot/languages", - "license": null, - "merges_url": "https://api.github.com/repos/ursa-labs/ursabot/merges", - "milestones_url": "https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}", - "mirror_url": null, - "name": "ursabot", - "node_id": "MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=", - "notifications_url": "https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}", - "open_issues": 19, - "open_issues_count": 19, - "owner": { - "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4", - "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}", - "followers_url": "https://api.github.com/users/ursa-labs/followers", - "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}", - "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}", - "gravatar_id": "", - "html_url": "https://github.com/ursa-labs", - "id": 46514972, - "login": "ursa-labs", - "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy", - "organizations_url": "https://api.github.com/users/ursa-labs/orgs", - "received_events_url": "https://api.github.com/users/ursa-labs/received_events", - "repos_url": "https://api.github.com/users/ursa-labs/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions", - "type": "Organization", - "url": "https://api.github.com/users/ursa-labs" - }, - "private": false, - "pulls_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}", - "pushed_at": "2019-04-05T11:22:16Z", - "releases_url": "https://api.github.com/repos/ursa-labs/ursabot/releases{/id}", - "size": 892, - "ssh_url": "git@github.com:ursa-labs/ursabot.git", - "stargazers_count": 1, - "stargazers_url": "https://api.github.com/repos/ursa-labs/ursabot/stargazers", - "statuses_url": "https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}", - "subscribers_url": "https://api.github.com/repos/ursa-labs/ursabot/subscribers", - "subscription_url": "https://api.github.com/repos/ursa-labs/ursabot/subscription", - "svn_url": "https://github.com/ursa-labs/ursabot", - "tags_url": "https://api.github.com/repos/ursa-labs/ursabot/tags", - "teams_url": "https://api.github.com/repos/ursa-labs/ursabot/teams", - "trees_url": "https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}", - "updated_at": "2019-04-04T17:49:10Z", - "url": "https://api.github.com/repos/ursa-labs/ursabot", - "watchers": 1, - "watchers_count": 1 - }, - "sender": { - "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4", - "events_url": "https://api.github.com/users/kszucs/events{/privacy}", - "followers_url": "https://api.github.com/users/kszucs/followers", - "following_url": "https://api.github.com/users/kszucs/following{/other_user}", - "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}", - "gravatar_id": "", - "html_url": "https://github.com/kszucs", - "id": 961747, - "login": "kszucs", - "node_id": "MDQ6VXNlcjk2MTc0Nw==", - "organizations_url": "https://api.github.com/users/kszucs/orgs", - "received_events_url": "https://api.github.com/users/kszucs/received_events", - "repos_url": "https://api.github.com/users/kszucs/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions", - "type": "User", - "url": "https://api.github.com/users/kszucs" - } -} \ No newline at end of file diff --git a/dev/archery/archery/tests/fixtures/event-issue-comment-with-empty-command.json b/dev/archery/archery/tests/fixtures/event-issue-comment-with-empty-command.json deleted file mode 100644 index c88197c8e024..000000000000 --- a/dev/archery/archery/tests/fixtures/event-issue-comment-with-empty-command.json +++ /dev/null @@ -1,217 +0,0 @@ -{ - "action": "created", - "comment": { - "author_association": "MEMBER", - "body": "@ursabot ", - "body_html": "", - "body_text": "", - "created_at": "2019-04-05T11:35:46Z", - "html_url": "https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480243811", - "id": 480243811, - "issue_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26", - "node_id": "MDEyOklzc3VlQ29tbWVudDQ4MDI0MzgxMQ==", - "updated_at": "2019-04-05T11:35:46Z", - "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480243811", - "user": { - "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4", - "events_url": "https://api.github.com/users/kszucs/events{/privacy}", - "followers_url": "https://api.github.com/users/kszucs/followers", - "following_url": "https://api.github.com/users/kszucs/following{/other_user}", - "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}", - "gravatar_id": "", - "html_url": "https://github.com/kszucs", - "id": 961747, - "login": "kszucs", - "node_id": "MDQ6VXNlcjk2MTc0Nw==", - "organizations_url": "https://api.github.com/users/kszucs/orgs", - "received_events_url": "https://api.github.com/users/kszucs/received_events", - "repos_url": "https://api.github.com/users/kszucs/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions", - "type": "User", - "url": "https://api.github.com/users/kszucs" - } - }, - "issue": { - "assignee": null, - "assignees": [], - "author_association": "MEMBER", - "body": "", - "body_html": "", - "body_text": "", - "closed_at": null, - "closed_by": null, - "comments": 1, - "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments", - "created_at": "2019-04-05T11:22:15Z", - "events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/events", - "html_url": "https://github.com/ursa-labs/ursabot/pull/26", - "id": 429706959, - "labels": [], - "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}", - "locked": false, - "milestone": null, - "node_id": "MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy", - "number": 26, - "pull_request": { - "diff_url": "https://github.com/ursa-labs/ursabot/pull/26.diff", - "html_url": "https://github.com/ursa-labs/ursabot/pull/26", - "patch_url": "https://github.com/ursa-labs/ursabot/pull/26.patch", - "url": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26" - }, - "repository_url": "https://api.github.com/repos/ursa-labs/ursabot", - "state": "open", - "title": "Unittests for GithubHook", - "updated_at": "2019-04-05T11:35:46Z", - "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26", - "user": { - "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4", - "events_url": "https://api.github.com/users/kszucs/events{/privacy}", - "followers_url": "https://api.github.com/users/kszucs/followers", - "following_url": "https://api.github.com/users/kszucs/following{/other_user}", - "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}", - "gravatar_id": "", - "html_url": "https://github.com/kszucs", - "id": 961747, - "login": "kszucs", - "node_id": "MDQ6VXNlcjk2MTc0Nw==", - "organizations_url": "https://api.github.com/users/kszucs/orgs", - "received_events_url": "https://api.github.com/users/kszucs/received_events", - "repos_url": "https://api.github.com/users/kszucs/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions", - "type": "User", - "url": "https://api.github.com/users/kszucs" - } - }, - "organization": { - "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4", - "description": "Innovation lab for open source data science tools, powered by Apache Arrow", - "events_url": "https://api.github.com/orgs/ursa-labs/events", - "hooks_url": "https://api.github.com/orgs/ursa-labs/hooks", - "id": 46514972, - "issues_url": "https://api.github.com/orgs/ursa-labs/issues", - "login": "ursa-labs", - "members_url": "https://api.github.com/orgs/ursa-labs/members{/member}", - "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy", - "public_members_url": "https://api.github.com/orgs/ursa-labs/public_members{/member}", - "repos_url": "https://api.github.com/orgs/ursa-labs/repos", - "url": "https://api.github.com/orgs/ursa-labs" - }, - "repository": { - "archive_url": "https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}", - "archived": false, - "assignees_url": "https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}", - "blobs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}", - "branches_url": "https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}", - "clone_url": "https://github.com/ursa-labs/ursabot.git", - "collaborators_url": "https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}", - "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/comments{/number}", - "commits_url": "https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}", - "compare_url": "https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}", - "contributors_url": "https://api.github.com/repos/ursa-labs/ursabot/contributors", - "created_at": "2019-02-04T15:40:31Z", - "default_branch": "master", - "deployments_url": "https://api.github.com/repos/ursa-labs/ursabot/deployments", - "description": null, - "disabled": false, - "downloads_url": "https://api.github.com/repos/ursa-labs/ursabot/downloads", - "events_url": "https://api.github.com/repos/ursa-labs/ursabot/events", - "fork": false, - "forks": 0, - "forks_count": 0, - "forks_url": "https://api.github.com/repos/ursa-labs/ursabot/forks", - "full_name": "ursa-labs/ursabot", - "git_commits_url": "https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}", - "git_refs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}", - "git_tags_url": "https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}", - "git_url": "git://github.com/ursa-labs/ursabot.git", - "has_downloads": true, - "has_issues": true, - "has_pages": false, - "has_projects": true, - "has_wiki": true, - "homepage": null, - "hooks_url": "https://api.github.com/repos/ursa-labs/ursabot/hooks", - "html_url": "https://github.com/ursa-labs/ursabot", - "id": 169101701, - "issue_comment_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}", - "issue_events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}", - "issues_url": "https://api.github.com/repos/ursa-labs/ursabot/issues{/number}", - "keys_url": "https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}", - "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/labels{/name}", - "language": "Jupyter Notebook", - "languages_url": "https://api.github.com/repos/ursa-labs/ursabot/languages", - "license": null, - "merges_url": "https://api.github.com/repos/ursa-labs/ursabot/merges", - "milestones_url": "https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}", - "mirror_url": null, - "name": "ursabot", - "node_id": "MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=", - "notifications_url": "https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}", - "open_issues": 19, - "open_issues_count": 19, - "owner": { - "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4", - "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}", - "followers_url": "https://api.github.com/users/ursa-labs/followers", - "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}", - "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}", - "gravatar_id": "", - "html_url": "https://github.com/ursa-labs", - "id": 46514972, - "login": "ursa-labs", - "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy", - "organizations_url": "https://api.github.com/users/ursa-labs/orgs", - "received_events_url": "https://api.github.com/users/ursa-labs/received_events", - "repos_url": "https://api.github.com/users/ursa-labs/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions", - "type": "Organization", - "url": "https://api.github.com/users/ursa-labs" - }, - "private": false, - "pulls_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}", - "pushed_at": "2019-04-05T11:22:16Z", - "releases_url": "https://api.github.com/repos/ursa-labs/ursabot/releases{/id}", - "size": 892, - "ssh_url": "git@github.com:ursa-labs/ursabot.git", - "stargazers_count": 1, - "stargazers_url": "https://api.github.com/repos/ursa-labs/ursabot/stargazers", - "statuses_url": "https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}", - "subscribers_url": "https://api.github.com/repos/ursa-labs/ursabot/subscribers", - "subscription_url": "https://api.github.com/repos/ursa-labs/ursabot/subscription", - "svn_url": "https://github.com/ursa-labs/ursabot", - "tags_url": "https://api.github.com/repos/ursa-labs/ursabot/tags", - "teams_url": "https://api.github.com/repos/ursa-labs/ursabot/teams", - "trees_url": "https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}", - "updated_at": "2019-04-04T17:49:10Z", - "url": "https://api.github.com/repos/ursa-labs/ursabot", - "watchers": 1, - "watchers_count": 1 - }, - "sender": { - "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4", - "events_url": "https://api.github.com/users/kszucs/events{/privacy}", - "followers_url": "https://api.github.com/users/kszucs/followers", - "following_url": "https://api.github.com/users/kszucs/following{/other_user}", - "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}", - "gravatar_id": "", - "html_url": "https://github.com/kszucs", - "id": 961747, - "login": "kszucs", - "node_id": "MDQ6VXNlcjk2MTc0Nw==", - "organizations_url": "https://api.github.com/users/kszucs/orgs", - "received_events_url": "https://api.github.com/users/kszucs/received_events", - "repos_url": "https://api.github.com/users/kszucs/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions", - "type": "User", - "url": "https://api.github.com/users/kszucs" - } -} \ No newline at end of file diff --git a/dev/archery/archery/tests/fixtures/event-issue-comment-without-pull-request.json b/dev/archery/archery/tests/fixtures/event-issue-comment-without-pull-request.json deleted file mode 100644 index 9e362fc0e1bc..000000000000 --- a/dev/archery/archery/tests/fixtures/event-issue-comment-without-pull-request.json +++ /dev/null @@ -1,206 +0,0 @@ -{ - "action": "created", - "comment": { - "author_association": "MEMBER", - "body": "@ursabot build", - "created_at": "2019-04-05T13:07:57Z", - "html_url": "https://github.com/ursa-labs/ursabot/issues/19#issuecomment-480268708", - "id": 480268708, - "issue_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/19", - "node_id": "MDEyOklzc3VlQ29tbWVudDQ4MDI2ODcwOA==", - "updated_at": "2019-04-05T13:07:57Z", - "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480268708", - "user": { - "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4", - "events_url": "https://api.github.com/users/kszucs/events{/privacy}", - "followers_url": "https://api.github.com/users/kszucs/followers", - "following_url": "https://api.github.com/users/kszucs/following{/other_user}", - "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}", - "gravatar_id": "", - "html_url": "https://github.com/kszucs", - "id": 961747, - "login": "kszucs", - "node_id": "MDQ6VXNlcjk2MTc0Nw==", - "organizations_url": "https://api.github.com/users/kszucs/orgs", - "received_events_url": "https://api.github.com/users/kszucs/received_events", - "repos_url": "https://api.github.com/users/kszucs/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions", - "type": "User", - "url": "https://api.github.com/users/kszucs" - } - }, - "issue": { - "assignee": null, - "assignees": [], - "author_association": "MEMBER", - "body": "", - "closed_at": null, - "comments": 5, - "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/19/comments", - "created_at": "2019-04-02T09:56:41Z", - "events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/19/events", - "html_url": "https://github.com/ursa-labs/ursabot/issues/19", - "id": 428131685, - "labels": [], - "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/19/labels{/name}", - "locked": false, - "milestone": null, - "node_id": "MDU6SXNzdWU0MjgxMzE2ODU=", - "number": 19, - "repository_url": "https://api.github.com/repos/ursa-labs/ursabot", - "state": "open", - "title": "Build ursabot itself via ursabot", - "updated_at": "2019-04-05T13:07:57Z", - "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/19", - "user": { - "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4", - "events_url": "https://api.github.com/users/kszucs/events{/privacy}", - "followers_url": "https://api.github.com/users/kszucs/followers", - "following_url": "https://api.github.com/users/kszucs/following{/other_user}", - "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}", - "gravatar_id": "", - "html_url": "https://github.com/kszucs", - "id": 961747, - "login": "kszucs", - "node_id": "MDQ6VXNlcjk2MTc0Nw==", - "organizations_url": "https://api.github.com/users/kszucs/orgs", - "received_events_url": "https://api.github.com/users/kszucs/received_events", - "repos_url": "https://api.github.com/users/kszucs/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions", - "type": "User", - "url": "https://api.github.com/users/kszucs" - } - }, - "organization": { - "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4", - "description": "Innovation lab for open source data science tools, powered by Apache Arrow", - "events_url": "https://api.github.com/orgs/ursa-labs/events", - "hooks_url": "https://api.github.com/orgs/ursa-labs/hooks", - "id": 46514972, - "issues_url": "https://api.github.com/orgs/ursa-labs/issues", - "login": "ursa-labs", - "members_url": "https://api.github.com/orgs/ursa-labs/members{/member}", - "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy", - "public_members_url": "https://api.github.com/orgs/ursa-labs/public_members{/member}", - "repos_url": "https://api.github.com/orgs/ursa-labs/repos", - "url": "https://api.github.com/orgs/ursa-labs" - }, - "repository": { - "archive_url": "https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}", - "archived": false, - "assignees_url": "https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}", - "blobs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}", - "branches_url": "https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}", - "clone_url": "https://github.com/ursa-labs/ursabot.git", - "collaborators_url": "https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}", - "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/comments{/number}", - "commits_url": "https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}", - "compare_url": "https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}", - "contributors_url": "https://api.github.com/repos/ursa-labs/ursabot/contributors", - "created_at": "2019-02-04T15:40:31Z", - "default_branch": "master", - "deployments_url": "https://api.github.com/repos/ursa-labs/ursabot/deployments", - "description": null, - "disabled": false, - "downloads_url": "https://api.github.com/repos/ursa-labs/ursabot/downloads", - "events_url": "https://api.github.com/repos/ursa-labs/ursabot/events", - "fork": false, - "forks": 0, - "forks_count": 0, - "forks_url": "https://api.github.com/repos/ursa-labs/ursabot/forks", - "full_name": "ursa-labs/ursabot", - "git_commits_url": "https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}", - "git_refs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}", - "git_tags_url": "https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}", - "git_url": "git://github.com/ursa-labs/ursabot.git", - "has_downloads": true, - "has_issues": true, - "has_pages": false, - "has_projects": true, - "has_wiki": true, - "homepage": null, - "hooks_url": "https://api.github.com/repos/ursa-labs/ursabot/hooks", - "html_url": "https://github.com/ursa-labs/ursabot", - "id": 169101701, - "issue_comment_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}", - "issue_events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}", - "issues_url": "https://api.github.com/repos/ursa-labs/ursabot/issues{/number}", - "keys_url": "https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}", - "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/labels{/name}", - "language": "Jupyter Notebook", - "languages_url": "https://api.github.com/repos/ursa-labs/ursabot/languages", - "license": null, - "merges_url": "https://api.github.com/repos/ursa-labs/ursabot/merges", - "milestones_url": "https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}", - "mirror_url": null, - "name": "ursabot", - "node_id": "MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=", - "notifications_url": "https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}", - "open_issues": 19, - "open_issues_count": 19, - "owner": { - "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4", - "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}", - "followers_url": "https://api.github.com/users/ursa-labs/followers", - "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}", - "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}", - "gravatar_id": "", - "html_url": "https://github.com/ursa-labs", - "id": 46514972, - "login": "ursa-labs", - "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy", - "organizations_url": "https://api.github.com/users/ursa-labs/orgs", - "received_events_url": "https://api.github.com/users/ursa-labs/received_events", - "repos_url": "https://api.github.com/users/ursa-labs/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions", - "type": "Organization", - "url": "https://api.github.com/users/ursa-labs" - }, - "private": false, - "pulls_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}", - "pushed_at": "2019-04-05T12:01:40Z", - "releases_url": "https://api.github.com/repos/ursa-labs/ursabot/releases{/id}", - "size": 898, - "ssh_url": "git@github.com:ursa-labs/ursabot.git", - "stargazers_count": 1, - "stargazers_url": "https://api.github.com/repos/ursa-labs/ursabot/stargazers", - "statuses_url": "https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}", - "subscribers_url": "https://api.github.com/repos/ursa-labs/ursabot/subscribers", - "subscription_url": "https://api.github.com/repos/ursa-labs/ursabot/subscription", - "svn_url": "https://github.com/ursa-labs/ursabot", - "tags_url": "https://api.github.com/repos/ursa-labs/ursabot/tags", - "teams_url": "https://api.github.com/repos/ursa-labs/ursabot/teams", - "trees_url": "https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}", - "updated_at": "2019-04-04T17:49:10Z", - "url": "https://api.github.com/repos/ursa-labs/ursabot", - "watchers": 1, - "watchers_count": 1 - }, - "sender": { - "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4", - "events_url": "https://api.github.com/users/kszucs/events{/privacy}", - "followers_url": "https://api.github.com/users/kszucs/followers", - "following_url": "https://api.github.com/users/kszucs/following{/other_user}", - "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}", - "gravatar_id": "", - "html_url": "https://github.com/kszucs", - "id": 961747, - "login": "kszucs", - "node_id": "MDQ6VXNlcjk2MTc0Nw==", - "organizations_url": "https://api.github.com/users/kszucs/orgs", - "received_events_url": "https://api.github.com/users/kszucs/received_events", - "repos_url": "https://api.github.com/users/kszucs/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions", - "type": "User", - "url": "https://api.github.com/users/kszucs" - } -} \ No newline at end of file diff --git a/dev/archery/archery/tests/fixtures/event-pull-request-opened.json b/dev/archery/archery/tests/fixtures/event-pull-request-opened.json deleted file mode 100644 index 9cf5c0dda784..000000000000 --- a/dev/archery/archery/tests/fixtures/event-pull-request-opened.json +++ /dev/null @@ -1,445 +0,0 @@ -{ - "action": "opened", - "number": 26, - "pull_request": { - "url": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26", - "id": 267785552, - "node_id": "MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy", - "html_url": "https://github.com/ursa-labs/ursabot/pull/26", - "diff_url": "https://github.com/ursa-labs/ursabot/pull/26.diff", - "patch_url": "https://github.com/ursa-labs/ursabot/pull/26.patch", - "issue_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26", - "number": 26, - "state": "open", - "locked": false, - "title": "Unittests for GithubHook", - "user": { - "login": "kszucs", - "id": 961747, - "node_id": "MDQ6VXNlcjk2MTc0Nw==", - "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/kszucs", - "html_url": "https://github.com/kszucs", - "followers_url": "https://api.github.com/users/kszucs/followers", - "following_url": "https://api.github.com/users/kszucs/following{/other_user}", - "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}", - "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions", - "organizations_url": "https://api.github.com/users/kszucs/orgs", - "repos_url": "https://api.github.com/users/kszucs/repos", - "events_url": "https://api.github.com/users/kszucs/events{/privacy}", - "received_events_url": "https://api.github.com/users/kszucs/received_events", - "type": "User", - "site_admin": false - }, - "body": "", - "created_at": "2019-04-05T11:22:15Z", - "updated_at": "2019-04-05T12:01:40Z", - "closed_at": null, - "merged_at": null, - "merge_commit_sha": "cc5dc3606988b3824be54df779ed2028776113cb", - "assignee": null, - "assignees": [], - "requested_reviewers": [], - "requested_teams": [], - "labels": [], - "milestone": null, - "commits_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26/commits", - "review_comments_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26/comments", - "review_comment_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls/comments{/number}", - "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments", - "statuses_url": "https://api.github.com/repos/ursa-labs/ursabot/statuses/2705da2b616b98fa6010a25813c5a7a27456f71d", - "head": { - "label": "ursa-labs:test-hook", - "ref": "test-hook", - "sha": "2705da2b616b98fa6010a25813c5a7a27456f71d", - "user": { - "login": "ursa-labs", - "id": 46514972, - "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy", - "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/ursa-labs", - "html_url": "https://github.com/ursa-labs", - "followers_url": "https://api.github.com/users/ursa-labs/followers", - "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}", - "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}", - "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions", - "organizations_url": "https://api.github.com/users/ursa-labs/orgs", - "repos_url": "https://api.github.com/users/ursa-labs/repos", - "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}", - "received_events_url": "https://api.github.com/users/ursa-labs/received_events", - "type": "Organization", - "site_admin": false - }, - "repo": { - "id": 169101701, - "node_id": "MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=", - "name": "ursabot", - "full_name": "ursa-labs/ursabot", - "private": false, - "owner": { - "login": "ursa-labs", - "id": 46514972, - "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy", - "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/ursa-labs", - "html_url": "https://github.com/ursa-labs", - "followers_url": "https://api.github.com/users/ursa-labs/followers", - "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}", - "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}", - "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions", - "organizations_url": "https://api.github.com/users/ursa-labs/orgs", - "repos_url": "https://api.github.com/users/ursa-labs/repos", - "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}", - "received_events_url": "https://api.github.com/users/ursa-labs/received_events", - "type": "Organization", - "site_admin": false - }, - "html_url": "https://github.com/ursa-labs/ursabot", - "description": null, - "fork": false, - "url": "https://api.github.com/repos/ursa-labs/ursabot", - "forks_url": "https://api.github.com/repos/ursa-labs/ursabot/forks", - "keys_url": "https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}", - "collaborators_url": "https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}", - "teams_url": "https://api.github.com/repos/ursa-labs/ursabot/teams", - "hooks_url": "https://api.github.com/repos/ursa-labs/ursabot/hooks", - "issue_events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}", - "events_url": "https://api.github.com/repos/ursa-labs/ursabot/events", - "assignees_url": "https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}", - "branches_url": "https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}", - "tags_url": "https://api.github.com/repos/ursa-labs/ursabot/tags", - "blobs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}", - "git_tags_url": "https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}", - "git_refs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}", - "trees_url": "https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}", - "statuses_url": "https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}", - "languages_url": "https://api.github.com/repos/ursa-labs/ursabot/languages", - "stargazers_url": "https://api.github.com/repos/ursa-labs/ursabot/stargazers", - "contributors_url": "https://api.github.com/repos/ursa-labs/ursabot/contributors", - "subscribers_url": "https://api.github.com/repos/ursa-labs/ursabot/subscribers", - "subscription_url": "https://api.github.com/repos/ursa-labs/ursabot/subscription", - "commits_url": "https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}", - "git_commits_url": "https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}", - "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/comments{/number}", - "issue_comment_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}", - "compare_url": "https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}", - "merges_url": "https://api.github.com/repos/ursa-labs/ursabot/merges", - "archive_url": "https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}", - "downloads_url": "https://api.github.com/repos/ursa-labs/ursabot/downloads", - "issues_url": "https://api.github.com/repos/ursa-labs/ursabot/issues{/number}", - "pulls_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}", - "milestones_url": "https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}", - "notifications_url": "https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}", - "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/labels{/name}", - "releases_url": "https://api.github.com/repos/ursa-labs/ursabot/releases{/id}", - "deployments_url": "https://api.github.com/repos/ursa-labs/ursabot/deployments", - "created_at": "2019-02-04T15:40:31Z", - "updated_at": "2019-04-04T17:49:10Z", - "pushed_at": "2019-04-05T12:01:40Z", - "git_url": "git://github.com/ursa-labs/ursabot.git", - "ssh_url": "git@github.com:ursa-labs/ursabot.git", - "clone_url": "https://github.com/ursa-labs/ursabot.git", - "svn_url": "https://github.com/ursa-labs/ursabot", - "homepage": null, - "size": 898, - "stargazers_count": 1, - "watchers_count": 1, - "language": "Jupyter Notebook", - "has_issues": true, - "has_projects": true, - "has_downloads": true, - "has_wiki": true, - "has_pages": false, - "forks_count": 0, - "mirror_url": null, - "archived": false, - "disabled": false, - "open_issues_count": 19, - "license": null, - "forks": 0, - "open_issues": 19, - "watchers": 1, - "default_branch": "master" - } - }, - "base": { - "label": "ursa-labs:master", - "ref": "master", - "sha": "a162ad254b589b924db47e057791191b39613fd5", - "user": { - "login": "ursa-labs", - "id": 46514972, - "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy", - "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/ursa-labs", - "html_url": "https://github.com/ursa-labs", - "followers_url": "https://api.github.com/users/ursa-labs/followers", - "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}", - "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}", - "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions", - "organizations_url": "https://api.github.com/users/ursa-labs/orgs", - "repos_url": "https://api.github.com/users/ursa-labs/repos", - "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}", - "received_events_url": "https://api.github.com/users/ursa-labs/received_events", - "type": "Organization", - "site_admin": false - }, - "repo": { - "id": 169101701, - "node_id": "MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=", - "name": "ursabot", - "full_name": "ursa-labs/ursabot", - "private": false, - "owner": { - "login": "ursa-labs", - "id": 46514972, - "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy", - "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/ursa-labs", - "html_url": "https://github.com/ursa-labs", - "followers_url": "https://api.github.com/users/ursa-labs/followers", - "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}", - "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}", - "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions", - "organizations_url": "https://api.github.com/users/ursa-labs/orgs", - "repos_url": "https://api.github.com/users/ursa-labs/repos", - "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}", - "received_events_url": "https://api.github.com/users/ursa-labs/received_events", - "type": "Organization", - "site_admin": false - }, - "html_url": "https://github.com/ursa-labs/ursabot", - "description": null, - "fork": false, - "url": "https://api.github.com/repos/ursa-labs/ursabot", - "forks_url": "https://api.github.com/repos/ursa-labs/ursabot/forks", - "keys_url": "https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}", - "collaborators_url": "https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}", - "teams_url": "https://api.github.com/repos/ursa-labs/ursabot/teams", - "hooks_url": "https://api.github.com/repos/ursa-labs/ursabot/hooks", - "issue_events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}", - "events_url": "https://api.github.com/repos/ursa-labs/ursabot/events", - "assignees_url": "https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}", - "branches_url": "https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}", - "tags_url": "https://api.github.com/repos/ursa-labs/ursabot/tags", - "blobs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}", - "git_tags_url": "https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}", - "git_refs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}", - "trees_url": "https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}", - "statuses_url": "https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}", - "languages_url": "https://api.github.com/repos/ursa-labs/ursabot/languages", - "stargazers_url": "https://api.github.com/repos/ursa-labs/ursabot/stargazers", - "contributors_url": "https://api.github.com/repos/ursa-labs/ursabot/contributors", - "subscribers_url": "https://api.github.com/repos/ursa-labs/ursabot/subscribers", - "subscription_url": "https://api.github.com/repos/ursa-labs/ursabot/subscription", - "commits_url": "https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}", - "git_commits_url": "https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}", - "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/comments{/number}", - "issue_comment_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}", - "compare_url": "https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}", - "merges_url": "https://api.github.com/repos/ursa-labs/ursabot/merges", - "archive_url": "https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}", - "downloads_url": "https://api.github.com/repos/ursa-labs/ursabot/downloads", - "issues_url": "https://api.github.com/repos/ursa-labs/ursabot/issues{/number}", - "pulls_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}", - "milestones_url": "https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}", - "notifications_url": "https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}", - "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/labels{/name}", - "releases_url": "https://api.github.com/repos/ursa-labs/ursabot/releases{/id}", - "deployments_url": "https://api.github.com/repos/ursa-labs/ursabot/deployments", - "created_at": "2019-02-04T15:40:31Z", - "updated_at": "2019-04-04T17:49:10Z", - "pushed_at": "2019-04-05T12:01:40Z", - "git_url": "git://github.com/ursa-labs/ursabot.git", - "ssh_url": "git@github.com:ursa-labs/ursabot.git", - "clone_url": "https://github.com/ursa-labs/ursabot.git", - "svn_url": "https://github.com/ursa-labs/ursabot", - "homepage": null, - "size": 898, - "stargazers_count": 1, - "watchers_count": 1, - "language": "Jupyter Notebook", - "has_issues": true, - "has_projects": true, - "has_downloads": true, - "has_wiki": true, - "has_pages": false, - "forks_count": 0, - "mirror_url": null, - "archived": false, - "disabled": false, - "open_issues_count": 19, - "license": null, - "forks": 0, - "open_issues": 19, - "watchers": 1, - "default_branch": "master" - } - }, - "_links": { - "self": { - "href": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26" - }, - "html": { - "href": "https://github.com/ursa-labs/ursabot/pull/26" - }, - "issue": { - "href": "https://api.github.com/repos/ursa-labs/ursabot/issues/26" - }, - "comments": { - "href": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments" - }, - "review_comments": { - "href": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26/comments" - }, - "review_comment": { - "href": "https://api.github.com/repos/ursa-labs/ursabot/pulls/comments{/number}" - }, - "commits": { - "href": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26/commits" - }, - "statuses": { - "href": "https://api.github.com/repos/ursa-labs/ursabot/statuses/2705da2b616b98fa6010a25813c5a7a27456f71d" - } - }, - "author_association": "MEMBER", - "merged": false, - "mergeable": true, - "rebaseable": true, - "mergeable_state": "unstable", - "merged_by": null, - "comments": 5, - "review_comments": 0, - "maintainer_can_modify": false, - "commits": 2, - "additions": 1124, - "deletions": 0, - "changed_files": 7 - }, - "repository": { - "archive_url": "https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}", - "archived": false, - "assignees_url": "https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}", - "blobs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}", - "branches_url": "https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}", - "clone_url": "https://github.com/ursa-labs/ursabot.git", - "collaborators_url": "https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}", - "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/comments{/number}", - "commits_url": "https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}", - "compare_url": "https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}", - "contributors_url": "https://api.github.com/repos/ursa-labs/ursabot/contributors", - "created_at": "2019-02-04T15:40:31Z", - "default_branch": "master", - "deployments_url": "https://api.github.com/repos/ursa-labs/ursabot/deployments", - "description": null, - "disabled": false, - "downloads_url": "https://api.github.com/repos/ursa-labs/ursabot/downloads", - "events_url": "https://api.github.com/repos/ursa-labs/ursabot/events", - "fork": false, - "forks": 0, - "forks_count": 0, - "forks_url": "https://api.github.com/repos/ursa-labs/ursabot/forks", - "full_name": "ursa-labs/ursabot", - "git_commits_url": "https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}", - "git_refs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}", - "git_tags_url": "https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}", - "git_url": "git://github.com/ursa-labs/ursabot.git", - "has_downloads": true, - "has_issues": true, - "has_pages": false, - "has_projects": true, - "has_wiki": true, - "homepage": null, - "hooks_url": "https://api.github.com/repos/ursa-labs/ursabot/hooks", - "html_url": "https://github.com/ursa-labs/ursabot", - "id": 169101701, - "issue_comment_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}", - "issue_events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}", - "issues_url": "https://api.github.com/repos/ursa-labs/ursabot/issues{/number}", - "keys_url": "https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}", - "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/labels{/name}", - "language": "Jupyter Notebook", - "languages_url": "https://api.github.com/repos/ursa-labs/ursabot/languages", - "license": null, - "merges_url": "https://api.github.com/repos/ursa-labs/ursabot/merges", - "milestones_url": "https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}", - "mirror_url": null, - "name": "ursabot", - "node_id": "MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=", - "notifications_url": "https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}", - "open_issues": 19, - "open_issues_count": 19, - "owner": { - "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4", - "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}", - "followers_url": "https://api.github.com/users/ursa-labs/followers", - "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}", - "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}", - "gravatar_id": "", - "html_url": "https://github.com/ursa-labs", - "id": 46514972, - "login": "ursa-labs", - "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy", - "organizations_url": "https://api.github.com/users/ursa-labs/orgs", - "received_events_url": "https://api.github.com/users/ursa-labs/received_events", - "repos_url": "https://api.github.com/users/ursa-labs/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions", - "type": "Organization", - "url": "https://api.github.com/users/ursa-labs" - }, - "private": false, - "pulls_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}", - "pushed_at": "2019-04-05T11:22:16Z", - "releases_url": "https://api.github.com/repos/ursa-labs/ursabot/releases{/id}", - "size": 892, - "ssh_url": "git@github.com:ursa-labs/ursabot.git", - "stargazers_count": 1, - "stargazers_url": "https://api.github.com/repos/ursa-labs/ursabot/stargazers", - "statuses_url": "https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}", - "subscribers_url": "https://api.github.com/repos/ursa-labs/ursabot/subscribers", - "subscription_url": "https://api.github.com/repos/ursa-labs/ursabot/subscription", - "svn_url": "https://github.com/ursa-labs/ursabot", - "tags_url": "https://api.github.com/repos/ursa-labs/ursabot/tags", - "teams_url": "https://api.github.com/repos/ursa-labs/ursabot/teams", - "trees_url": "https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}", - "updated_at": "2019-04-04T17:49:10Z", - "url": "https://api.github.com/repos/ursa-labs/ursabot", - "watchers": 1, - "watchers_count": 1 - }, - "sender": { - "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4", - "events_url": "https://api.github.com/users/kszucs/events{/privacy}", - "followers_url": "https://api.github.com/users/kszucs/followers", - "following_url": "https://api.github.com/users/kszucs/following{/other_user}", - "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}", - "gravatar_id": "", - "html_url": "https://github.com/kszucs", - "id": 961747, - "login": "kszucs", - "node_id": "MDQ6VXNlcjk2MTc0Nw==", - "organizations_url": "https://api.github.com/users/kszucs/orgs", - "received_events_url": "https://api.github.com/users/kszucs/received_events", - "repos_url": "https://api.github.com/users/kszucs/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions", - "type": "User", - "url": "https://api.github.com/users/kszucs" - } -} \ No newline at end of file diff --git a/dev/archery/archery/tests/fixtures/issue-19.json b/dev/archery/archery/tests/fixtures/issue-19.json deleted file mode 100644 index 1e49397765e1..000000000000 --- a/dev/archery/archery/tests/fixtures/issue-19.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/19", - "repository_url": "https://api.github.com/repos/ursa-labs/ursabot", - "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/19/labels{/name}", - "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/19/comments", - "events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/19/events", - "html_url": "https://github.com/ursa-labs/ursabot/issues/19", - "id": 428131685, - "node_id": "MDU6SXNzdWU0MjgxMzE2ODU=", - "number": 19, - "title": "Build ursabot itself via ursabot", - "user": { - "login": "kszucs", - "id": 961747, - "node_id": "MDQ6VXNlcjk2MTc0Nw==", - "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/kszucs", - "html_url": "https://github.com/kszucs", - "followers_url": "https://api.github.com/users/kszucs/followers", - "following_url": "https://api.github.com/users/kszucs/following{/other_user}", - "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}", - "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions", - "organizations_url": "https://api.github.com/users/kszucs/orgs", - "repos_url": "https://api.github.com/users/kszucs/repos", - "events_url": "https://api.github.com/users/kszucs/events{/privacy}", - "received_events_url": "https://api.github.com/users/kszucs/received_events", - "type": "User", - "site_admin": false - }, - "labels": [], - "state": "closed", - "locked": false, - "assignee": null, - "assignees": [], - "milestone": null, - "comments": 8, - "created_at": "2019-04-02T09:56:41Z", - "updated_at": "2019-04-05T13:30:49Z", - "closed_at": "2019-04-05T13:30:49Z", - "author_association": "MEMBER", - "body": "", - "closed_by": { - "login": "kszucs", - "id": 961747, - "node_id": "MDQ6VXNlcjk2MTc0Nw==", - "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/kszucs", - "html_url": "https://github.com/kszucs", - "followers_url": "https://api.github.com/users/kszucs/followers", - "following_url": "https://api.github.com/users/kszucs/following{/other_user}", - "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}", - "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions", - "organizations_url": "https://api.github.com/users/kszucs/orgs", - "repos_url": "https://api.github.com/users/kszucs/repos", - "events_url": "https://api.github.com/users/kszucs/events{/privacy}", - "received_events_url": "https://api.github.com/users/kszucs/received_events", - "type": "User", - "site_admin": false - } -} \ No newline at end of file diff --git a/dev/archery/archery/tests/fixtures/issue-26.json b/dev/archery/archery/tests/fixtures/issue-26.json deleted file mode 100644 index 44c4d3bedef4..000000000000 --- a/dev/archery/archery/tests/fixtures/issue-26.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26", - "repository_url": "https://api.github.com/repos/ursa-labs/ursabot", - "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}", - "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments", - "events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/events", - "html_url": "https://github.com/ursa-labs/ursabot/pull/26", - "id": 429706959, - "node_id": "MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy", - "number": 26, - "title": "Unittests for GithubHook + native asyncio syntax", - "user": { - "login": "kszucs", - "id": 961747, - "node_id": "MDQ6VXNlcjk2MTc0Nw==", - "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/kszucs", - "html_url": "https://github.com/kszucs", - "followers_url": "https://api.github.com/users/kszucs/followers", - "following_url": "https://api.github.com/users/kszucs/following{/other_user}", - "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}", - "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions", - "organizations_url": "https://api.github.com/users/kszucs/orgs", - "repos_url": "https://api.github.com/users/kszucs/repos", - "events_url": "https://api.github.com/users/kszucs/events{/privacy}", - "received_events_url": "https://api.github.com/users/kszucs/received_events", - "type": "User", - "site_admin": false - }, - "labels": [], - "state": "closed", - "locked": false, - "assignee": null, - "assignees": [], - "milestone": null, - "comments": 9, - "created_at": "2019-04-05T11:22:15Z", - "updated_at": "2019-08-28T00:34:19Z", - "closed_at": "2019-04-05T13:54:34Z", - "author_association": "MEMBER", - "pull_request": { - "url": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26", - "html_url": "https://github.com/ursa-labs/ursabot/pull/26", - "diff_url": "https://github.com/ursa-labs/ursabot/pull/26.diff", - "patch_url": "https://github.com/ursa-labs/ursabot/pull/26.patch" - }, - "body": "Resolves:\r\n- #26 Unittests for GithubHook + native asyncio syntax\r\n- #27 Use native async/await keywords instead of @inlineCallbacks and yield\r\n", - "closed_by": { - "login": "kszucs", - "id": 961747, - "node_id": "MDQ6VXNlcjk2MTc0Nw==", - "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/kszucs", - "html_url": "https://github.com/kszucs", - "followers_url": "https://api.github.com/users/kszucs/followers", - "following_url": "https://api.github.com/users/kszucs/following{/other_user}", - "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}", - "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions", - "organizations_url": "https://api.github.com/users/kszucs/orgs", - "repos_url": "https://api.github.com/users/kszucs/repos", - "events_url": "https://api.github.com/users/kszucs/events{/privacy}", - "received_events_url": "https://api.github.com/users/kszucs/received_events", - "type": "User", - "site_admin": false - } -} \ No newline at end of file diff --git a/dev/archery/archery/tests/fixtures/issue-comment-480243811.json b/dev/archery/archery/tests/fixtures/issue-comment-480243811.json deleted file mode 100644 index 93ee4b13cd46..000000000000 --- a/dev/archery/archery/tests/fixtures/issue-comment-480243811.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments/479081273", - "html_url": "https://github.com/ursa-labs/ursabot/pull/21#issuecomment-479081273", - "issue_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/21", - "id": 480243811, - "node_id": "MDEyOklzc3VlQ29tbWVudDQ3OTA4MTI3Mw==", - "user": { - "login": "kszucs", - "id": 961747, - "node_id": "MDQ6VXNlcjk2MTc0Nw==", - "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/kszucs", - "html_url": "https://github.com/kszucs", - "followers_url": "https://api.github.com/users/kszucs/followers", - "following_url": "https://api.github.com/users/kszucs/following{/other_user}", - "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}", - "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions", - "organizations_url": "https://api.github.com/users/kszucs/orgs", - "repos_url": "https://api.github.com/users/kszucs/repos", - "events_url": "https://api.github.com/users/kszucs/events{/privacy}", - "received_events_url": "https://api.github.com/users/kszucs/received_events", - "type": "User", - "site_admin": false - }, - "created_at": "2019-04-02T16:29:46Z", - "updated_at": "2019-04-02T16:29:46Z", - "author_association": "MEMBER", - "body": "@ursabot" -} \ No newline at end of file diff --git a/dev/archery/archery/tests/fixtures/issue-comment-480248726.json b/dev/archery/archery/tests/fixtures/issue-comment-480248726.json deleted file mode 100644 index f3cd34083ed1..000000000000 --- a/dev/archery/archery/tests/fixtures/issue-comment-480248726.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480248726", - "html_url": "https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480248726", - "issue_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26", - "id": 480248726, - "node_id": "MDEyOklzc3VlQ29tbWVudDQ4MDI0ODcyNg==", - "user": { - "login": "kszucs", - "id": 961747, - "node_id": "MDQ6VXNlcjk2MTc0Nw==", - "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/kszucs", - "html_url": "https://github.com/kszucs", - "followers_url": "https://api.github.com/users/kszucs/followers", - "following_url": "https://api.github.com/users/kszucs/following{/other_user}", - "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}", - "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions", - "organizations_url": "https://api.github.com/users/kszucs/orgs", - "repos_url": "https://api.github.com/users/kszucs/repos", - "events_url": "https://api.github.com/users/kszucs/events{/privacy}", - "received_events_url": "https://api.github.com/users/kszucs/received_events", - "type": "User", - "site_admin": false - }, - "created_at": "2019-04-05T11:55:43Z", - "updated_at": "2019-04-05T11:55:43Z", - "author_association": "MEMBER", - "body": "@ursabot build" -} \ No newline at end of file diff --git a/dev/archery/archery/tests/fixtures/pull-request-26-commit.json b/dev/archery/archery/tests/fixtures/pull-request-26-commit.json deleted file mode 100644 index ffc48943a6ca..000000000000 --- a/dev/archery/archery/tests/fixtures/pull-request-26-commit.json +++ /dev/null @@ -1,158 +0,0 @@ -{ - "sha": "2705da2b616b98fa6010a25813c5a7a27456f71d", - "node_id": "MDY6Q29tbWl0MTY5MTAxNzAxOjI3MDVkYTJiNjE2Yjk4ZmE2MDEwYTI1ODEzYzVhN2EyNzQ1NmY3MWQ=", - "commit": { - "author": { - "name": "Krisztián Szűcs", - "email": "szucs.krisztian@gmail.com", - "date": "2019-04-05T12:01:31Z" - }, - "committer": { - "name": "Krisztián Szűcs", - "email": "szucs.krisztian@gmail.com", - "date": "2019-04-05T12:01:31Z" - }, - "message": "add recorded event requests", - "tree": { - "sha": "16a7bb186833a67e9c2d84a58393503b85500ceb", - "url": "https://api.github.com/repos/ursa-labs/ursabot/git/trees/16a7bb186833a67e9c2d84a58393503b85500ceb" - }, - "url": "https://api.github.com/repos/ursa-labs/ursabot/git/commits/2705da2b616b98fa6010a25813c5a7a27456f71d", - "comment_count": 0, - "verification": { - "verified": true, - "reason": "valid", - "signature": "-----BEGIN PGP SIGNATURE-----\n\niQFOBAABCAA4FiEEOOW2r8dr6sA77zHlgjqBKYe1QKUFAlynQ58aHHN6dWNzLmty\naXN6dGlhbkBnbWFpbC5jb20ACgkQgjqBKYe1QKUYKwf6AiXDMaLqNLNSjRY7lIXX\nudioewz0hSb4bgIXBv30nswu9CoOA0+mHCokEVtZhYbXzXDsZ1KJrilSC4j+Ws4q\nkRGA6iEmrne2HcSKNZXzcVnwV9zpwKxlVh2QCTNb1PuOYFBLH0kwE704uWIWMGDN\nbo8cjQPwegePCRguCvPh/5wa5J3uiq5gmJLG6bC/d1XYE+FJVtlnyzqzLMIryGKe\ntIciw+wwkF413Q/YVbZ49vLUeCX9H8PHC4mZYGDWuvjFW1WTfkjK5bAH+oaTVM6h\n350I5ZFloHmMA/QeRge5qFxXoEBMDGiXHHktzYZDXnliFOQNxzqwirA5lQQ6LRSS\naQ==\n=7rqi\n-----END PGP SIGNATURE-----", - "payload": "tree 16a7bb186833a67e9c2d84a58393503b85500ceb\nparent 446ae69b9385e8d0f40aa9595f723d34383af2f7\nauthor Krisztián Szűcs 1554465691 +0200\ncommitter Krisztián Szűcs 1554465691 +0200\n\nadd recorded event requests\n" - } - }, - "url": "https://api.github.com/repos/ursa-labs/ursabot/commits/2705da2b616b98fa6010a25813c5a7a27456f71d", - "html_url": "https://github.com/ursa-labs/ursabot/commit/2705da2b616b98fa6010a25813c5a7a27456f71d", - "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/commits/2705da2b616b98fa6010a25813c5a7a27456f71d/comments", - "author": { - "login": "kszucs", - "id": 961747, - "node_id": "MDQ6VXNlcjk2MTc0Nw==", - "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/kszucs", - "html_url": "https://github.com/kszucs", - "followers_url": "https://api.github.com/users/kszucs/followers", - "following_url": "https://api.github.com/users/kszucs/following{/other_user}", - "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}", - "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions", - "organizations_url": "https://api.github.com/users/kszucs/orgs", - "repos_url": "https://api.github.com/users/kszucs/repos", - "events_url": "https://api.github.com/users/kszucs/events{/privacy}", - "received_events_url": "https://api.github.com/users/kszucs/received_events", - "type": "User", - "site_admin": false - }, - "committer": { - "login": "kszucs", - "id": 961747, - "node_id": "MDQ6VXNlcjk2MTc0Nw==", - "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/kszucs", - "html_url": "https://github.com/kszucs", - "followers_url": "https://api.github.com/users/kszucs/followers", - "following_url": "https://api.github.com/users/kszucs/following{/other_user}", - "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}", - "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions", - "organizations_url": "https://api.github.com/users/kszucs/orgs", - "repos_url": "https://api.github.com/users/kszucs/repos", - "events_url": "https://api.github.com/users/kszucs/events{/privacy}", - "received_events_url": "https://api.github.com/users/kszucs/received_events", - "type": "User", - "site_admin": false - }, - "parents": [ - { - "sha": "446ae69b9385e8d0f40aa9595f723d34383af2f7", - "url": "https://api.github.com/repos/ursa-labs/ursabot/commits/446ae69b9385e8d0f40aa9595f723d34383af2f7", - "html_url": "https://github.com/ursa-labs/ursabot/commit/446ae69b9385e8d0f40aa9595f723d34383af2f7" - } - ], - "stats": { - "total": 1062, - "additions": 1058, - "deletions": 4 - }, - "files": [ - { - "sha": "dfae6eeaef384ae6180c6302a58b49e39982dc33", - "filename": "ursabot/tests/fixtures/issue-comment-build-command.json", - "status": "added", - "additions": 212, - "deletions": 0, - "changes": 212, - "blob_url": "https://github.com/ursa-labs/ursabot/blob/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-build-command.json", - "raw_url": "https://github.com/ursa-labs/ursabot/raw/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-build-command.json", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-build-command.json?ref=2705da2b616b98fa6010a25813c5a7a27456f71d", - "patch": "@@ -0,0 +1,212 @@\n+{\n+ \"action\": \"created\",\n+ \"comment\": {\n+ \"author_association\": \"NONE\",\n+ \"body\": \"I've successfully started builds for this PR\",\n+ \"created_at\": \"2019-04-05T11:55:44Z\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480248730\",\n+ \"id\": 480248730,\n+ \"issue_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+ \"node_id\": \"MDEyOklzc3VlQ29tbWVudDQ4MDI0ODczMA==\",\n+ \"updated_at\": \"2019-04-05T11:55:44Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480248730\",\n+ \"user\": {\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/49275095?v=4\",\n+ \"events_url\": \"https://api.github.com/users/ursabot/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/ursabot/followers\",\n+ \"following_url\": \"https://api.github.com/users/ursabot/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/ursabot/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/ursabot\",\n+ \"id\": 49275095,\n+ \"login\": \"ursabot\",\n+ \"node_id\": \"MDQ6VXNlcjQ5Mjc1MDk1\",\n+ \"organizations_url\": \"https://api.github.com/users/ursabot/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/ursabot/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/ursabot/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/ursabot/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/ursabot/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/ursabot\"\n+ }\n+ },\n+ \"issue\": {\n+ \"assignee\": null,\n+ \"assignees\": [],\n+ \"author_association\": \"MEMBER\",\n+ \"body\": \"\",\n+ \"closed_at\": null,\n+ \"comments\": 4,\n+ \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments\",\n+ \"created_at\": \"2019-04-05T11:22:15Z\",\n+ \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/events\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+ \"id\": 429706959,\n+ \"labels\": [],\n+ \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}\",\n+ \"locked\": false,\n+ \"milestone\": null,\n+ \"node_id\": \"MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy\",\n+ \"number\": 26,\n+ \"pull_request\": {\n+ \"diff_url\": \"https://github.com/ursa-labs/ursabot/pull/26.diff\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+ \"patch_url\": \"https://github.com/ursa-labs/ursabot/pull/26.patch\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/26\"\n+ },\n+ \"repository_url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+ \"state\": \"open\",\n+ \"title\": \"Unittests for GithubHook\",\n+ \"updated_at\": \"2019-04-05T11:55:44Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+ \"user\": {\n+ \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+ \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+ \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/kszucs\",\n+ \"id\": 961747,\n+ \"login\": \"kszucs\",\n+ \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+ \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/kszucs\"\n+ }\n+ },\n+ \"organization\": {\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+ \"description\": \"Innovation lab for open source data science tools, powered by Apache Arrow\",\n+ \"events_url\": \"https://api.github.com/orgs/ursa-labs/events\",\n+ \"hooks_url\": \"https://api.github.com/orgs/ursa-labs/hooks\",\n+ \"id\": 46514972,\n+ \"issues_url\": \"https://api.github.com/orgs/ursa-labs/issues\",\n+ \"login\": \"ursa-labs\",\n+ \"members_url\": \"https://api.github.com/orgs/ursa-labs/members{/member}\",\n+ \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+ \"public_members_url\": \"https://api.github.com/orgs/ursa-labs/public_members{/member}\",\n+ \"repos_url\": \"https://api.github.com/orgs/ursa-labs/repos\",\n+ \"url\": \"https://api.github.com/orgs/ursa-labs\"\n+ },\n+ \"repository\": {\n+ \"archive_url\": \"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\",\n+ \"archived\": false,\n+ \"assignees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\",\n+ \"blobs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\",\n+ \"branches_url\": \"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\",\n+ \"clone_url\": \"https://github.com/ursa-labs/ursabot.git\",\n+ \"collaborators_url\": \"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\",\n+ \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\",\n+ \"commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\",\n+ \"compare_url\": \"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\",\n+ \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\",\n+ \"contributors_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contributors\",\n+ \"created_at\": \"2019-02-04T15:40:31Z\",\n+ \"default_branch\": \"master\",\n+ \"deployments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/deployments\",\n+ \"description\": null,\n+ \"disabled\": false,\n+ \"downloads_url\": \"https://api.github.com/repos/ursa-labs/ursabot/downloads\",\n+ \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/events\",\n+ \"fork\": false,\n+ \"forks\": 0,\n+ \"forks_count\": 0,\n+ \"forks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/forks\",\n+ \"full_name\": \"ursa-labs/ursabot\",\n+ \"git_commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\",\n+ \"git_refs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\",\n+ \"git_tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\",\n+ \"git_url\": \"git://github.com/ursa-labs/ursabot.git\",\n+ \"has_downloads\": true,\n+ \"has_issues\": true,\n+ \"has_pages\": false,\n+ \"has_projects\": true,\n+ \"has_wiki\": true,\n+ \"homepage\": null,\n+ \"hooks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/hooks\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot\",\n+ \"id\": 169101701,\n+ \"issue_comment_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\",\n+ \"issue_events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\",\n+ \"issues_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\",\n+ \"keys_url\": \"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\",\n+ \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\",\n+ \"language\": \"Jupyter Notebook\",\n+ \"languages_url\": \"https://api.github.com/repos/ursa-labs/ursabot/languages\",\n+ \"license\": null,\n+ \"merges_url\": \"https://api.github.com/repos/ursa-labs/ursabot/merges\",\n+ \"milestones_url\": \"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\",\n+ \"mirror_url\": null,\n+ \"name\": \"ursabot\",\n+ \"node_id\": \"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\",\n+ \"notifications_url\": \"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\",\n+ \"open_issues\": 19,\n+ \"open_issues_count\": 19,\n+ \"owner\": {\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+ \"events_url\": \"https://api.github.com/users/ursa-labs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/ursa-labs/followers\",\n+ \"following_url\": \"https://api.github.com/users/ursa-labs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/ursa-labs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/ursa-labs\",\n+ \"id\": 46514972,\n+ \"login\": \"ursa-labs\",\n+ \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+ \"organizations_url\": \"https://api.github.com/users/ursa-labs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/ursa-labs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/ursa-labs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/ursa-labs/subscriptions\",\n+ \"type\": \"Organization\",\n+ \"url\": \"https://api.github.com/users/ursa-labs\"\n+ },\n+ \"private\": false,\n+ \"pulls_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\",\n+ \"pushed_at\": \"2019-04-05T11:22:16Z\",\n+ \"releases_url\": \"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\",\n+ \"size\": 892,\n+ \"ssh_url\": \"git@github.com:ursa-labs/ursabot.git\",\n+ \"stargazers_count\": 1,\n+ \"stargazers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/stargazers\",\n+ \"statuses_url\": \"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\",\n+ \"subscribers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscribers\",\n+ \"subscription_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscription\",\n+ \"svn_url\": \"https://github.com/ursa-labs/ursabot\",\n+ \"tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/tags\",\n+ \"teams_url\": \"https://api.github.com/repos/ursa-labs/ursabot/teams\",\n+ \"trees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\",\n+ \"updated_at\": \"2019-04-04T17:49:10Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+ \"watchers\": 1,\n+ \"watchers_count\": 1\n+ },\n+ \"sender\": {\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/49275095?v=4\",\n+ \"events_url\": \"https://api.github.com/users/ursabot/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/ursabot/followers\",\n+ \"following_url\": \"https://api.github.com/users/ursabot/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/ursabot/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/ursabot\",\n+ \"id\": 49275095,\n+ \"login\": \"ursabot\",\n+ \"node_id\": \"MDQ6VXNlcjQ5Mjc1MDk1\",\n+ \"organizations_url\": \"https://api.github.com/users/ursabot/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/ursabot/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/ursabot/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/ursabot/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/ursabot/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/ursabot\"\n+ }\n+}" - }, - { - "sha": "7ef554e333327f0e62aa1fd76b4b17844a39adeb", - "filename": "ursabot/tests/fixtures/issue-comment-by-ursabot.json", - "status": "added", - "additions": 212, - "deletions": 0, - "changes": 212, - "blob_url": "https://github.com/ursa-labs/ursabot/blob/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-by-ursabot.json", - "raw_url": "https://github.com/ursa-labs/ursabot/raw/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-by-ursabot.json", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-by-ursabot.json?ref=2705da2b616b98fa6010a25813c5a7a27456f71d", - "patch": "@@ -0,0 +1,212 @@\n+{\n+ \"action\": \"created\",\n+ \"comment\": {\n+ \"author_association\": \"NONE\",\n+ \"body\": \"Unknown command \\\"\\\"\",\n+ \"created_at\": \"2019-04-05T11:35:47Z\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480243815\",\n+ \"id\": 480243815,\n+ \"issue_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+ \"node_id\": \"MDEyOklzc3VlQ29tbWVudDQ4MDI0MzgxNQ==\",\n+ \"updated_at\": \"2019-04-05T11:35:47Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480243815\",\n+ \"user\": {\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/49275095?v=4\",\n+ \"events_url\": \"https://api.github.com/users/ursabot/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/ursabot/followers\",\n+ \"following_url\": \"https://api.github.com/users/ursabot/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/ursabot/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/ursabot\",\n+ \"id\": 49275095,\n+ \"login\": \"ursabot\",\n+ \"node_id\": \"MDQ6VXNlcjQ5Mjc1MDk1\",\n+ \"organizations_url\": \"https://api.github.com/users/ursabot/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/ursabot/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/ursabot/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/ursabot/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/ursabot/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/ursabot\"\n+ }\n+ },\n+ \"issue\": {\n+ \"assignee\": null,\n+ \"assignees\": [],\n+ \"author_association\": \"MEMBER\",\n+ \"body\": \"\",\n+ \"closed_at\": null,\n+ \"comments\": 2,\n+ \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments\",\n+ \"created_at\": \"2019-04-05T11:22:15Z\",\n+ \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/events\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+ \"id\": 429706959,\n+ \"labels\": [],\n+ \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}\",\n+ \"locked\": false,\n+ \"milestone\": null,\n+ \"node_id\": \"MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy\",\n+ \"number\": 26,\n+ \"pull_request\": {\n+ \"diff_url\": \"https://github.com/ursa-labs/ursabot/pull/26.diff\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+ \"patch_url\": \"https://github.com/ursa-labs/ursabot/pull/26.patch\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/26\"\n+ },\n+ \"repository_url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+ \"state\": \"open\",\n+ \"title\": \"Unittests for GithubHook\",\n+ \"updated_at\": \"2019-04-05T11:35:47Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+ \"user\": {\n+ \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+ \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+ \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/kszucs\",\n+ \"id\": 961747,\n+ \"login\": \"kszucs\",\n+ \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+ \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/kszucs\"\n+ }\n+ },\n+ \"organization\": {\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+ \"description\": \"Innovation lab for open source data science tools, powered by Apache Arrow\",\n+ \"events_url\": \"https://api.github.com/orgs/ursa-labs/events\",\n+ \"hooks_url\": \"https://api.github.com/orgs/ursa-labs/hooks\",\n+ \"id\": 46514972,\n+ \"issues_url\": \"https://api.github.com/orgs/ursa-labs/issues\",\n+ \"login\": \"ursa-labs\",\n+ \"members_url\": \"https://api.github.com/orgs/ursa-labs/members{/member}\",\n+ \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+ \"public_members_url\": \"https://api.github.com/orgs/ursa-labs/public_members{/member}\",\n+ \"repos_url\": \"https://api.github.com/orgs/ursa-labs/repos\",\n+ \"url\": \"https://api.github.com/orgs/ursa-labs\"\n+ },\n+ \"repository\": {\n+ \"archive_url\": \"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\",\n+ \"archived\": false,\n+ \"assignees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\",\n+ \"blobs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\",\n+ \"branches_url\": \"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\",\n+ \"clone_url\": \"https://github.com/ursa-labs/ursabot.git\",\n+ \"collaborators_url\": \"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\",\n+ \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\",\n+ \"commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\",\n+ \"compare_url\": \"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\",\n+ \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\",\n+ \"contributors_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contributors\",\n+ \"created_at\": \"2019-02-04T15:40:31Z\",\n+ \"default_branch\": \"master\",\n+ \"deployments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/deployments\",\n+ \"description\": null,\n+ \"disabled\": false,\n+ \"downloads_url\": \"https://api.github.com/repos/ursa-labs/ursabot/downloads\",\n+ \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/events\",\n+ \"fork\": false,\n+ \"forks\": 0,\n+ \"forks_count\": 0,\n+ \"forks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/forks\",\n+ \"full_name\": \"ursa-labs/ursabot\",\n+ \"git_commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\",\n+ \"git_refs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\",\n+ \"git_tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\",\n+ \"git_url\": \"git://github.com/ursa-labs/ursabot.git\",\n+ \"has_downloads\": true,\n+ \"has_issues\": true,\n+ \"has_pages\": false,\n+ \"has_projects\": true,\n+ \"has_wiki\": true,\n+ \"homepage\": null,\n+ \"hooks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/hooks\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot\",\n+ \"id\": 169101701,\n+ \"issue_comment_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\",\n+ \"issue_events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\",\n+ \"issues_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\",\n+ \"keys_url\": \"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\",\n+ \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\",\n+ \"language\": \"Jupyter Notebook\",\n+ \"languages_url\": \"https://api.github.com/repos/ursa-labs/ursabot/languages\",\n+ \"license\": null,\n+ \"merges_url\": \"https://api.github.com/repos/ursa-labs/ursabot/merges\",\n+ \"milestones_url\": \"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\",\n+ \"mirror_url\": null,\n+ \"name\": \"ursabot\",\n+ \"node_id\": \"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\",\n+ \"notifications_url\": \"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\",\n+ \"open_issues\": 19,\n+ \"open_issues_count\": 19,\n+ \"owner\": {\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+ \"events_url\": \"https://api.github.com/users/ursa-labs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/ursa-labs/followers\",\n+ \"following_url\": \"https://api.github.com/users/ursa-labs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/ursa-labs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/ursa-labs\",\n+ \"id\": 46514972,\n+ \"login\": \"ursa-labs\",\n+ \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+ \"organizations_url\": \"https://api.github.com/users/ursa-labs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/ursa-labs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/ursa-labs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/ursa-labs/subscriptions\",\n+ \"type\": \"Organization\",\n+ \"url\": \"https://api.github.com/users/ursa-labs\"\n+ },\n+ \"private\": false,\n+ \"pulls_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\",\n+ \"pushed_at\": \"2019-04-05T11:22:16Z\",\n+ \"releases_url\": \"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\",\n+ \"size\": 892,\n+ \"ssh_url\": \"git@github.com:ursa-labs/ursabot.git\",\n+ \"stargazers_count\": 1,\n+ \"stargazers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/stargazers\",\n+ \"statuses_url\": \"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\",\n+ \"subscribers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscribers\",\n+ \"subscription_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscription\",\n+ \"svn_url\": \"https://github.com/ursa-labs/ursabot\",\n+ \"tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/tags\",\n+ \"teams_url\": \"https://api.github.com/repos/ursa-labs/ursabot/teams\",\n+ \"trees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\",\n+ \"updated_at\": \"2019-04-04T17:49:10Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+ \"watchers\": 1,\n+ \"watchers_count\": 1\n+ },\n+ \"sender\": {\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/49275095?v=4\",\n+ \"events_url\": \"https://api.github.com/users/ursabot/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/ursabot/followers\",\n+ \"following_url\": \"https://api.github.com/users/ursabot/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/ursabot/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/ursabot\",\n+ \"id\": 49275095,\n+ \"login\": \"ursabot\",\n+ \"node_id\": \"MDQ6VXNlcjQ5Mjc1MDk1\",\n+ \"organizations_url\": \"https://api.github.com/users/ursabot/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/ursabot/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/ursabot/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/ursabot/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/ursabot/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/ursabot\"\n+ }\n+}" - }, - { - "sha": "a8082dbc91fdfe815b795e49ec10e49000771ef5", - "filename": "ursabot/tests/fixtures/issue-comment-not-mentioning-ursabot.json", - "status": "added", - "additions": 212, - "deletions": 0, - "changes": 212, - "blob_url": "https://github.com/ursa-labs/ursabot/blob/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-not-mentioning-ursabot.json", - "raw_url": "https://github.com/ursa-labs/ursabot/raw/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-not-mentioning-ursabot.json", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-not-mentioning-ursabot.json?ref=2705da2b616b98fa6010a25813c5a7a27456f71d", - "patch": "@@ -0,0 +1,212 @@\n+{\n+ \"action\": \"created\",\n+ \"comment\": {\n+ \"author_association\": \"MEMBER\",\n+ \"body\": \"bear is no game\",\n+ \"created_at\": \"2019-04-05T11:26:56Z\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480241727\",\n+ \"id\": 480241727,\n+ \"issue_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+ \"node_id\": \"MDEyOklzc3VlQ29tbWVudDQ4MDI0MTcyNw==\",\n+ \"updated_at\": \"2019-04-05T11:26:56Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480241727\",\n+ \"user\": {\n+ \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+ \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+ \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/kszucs\",\n+ \"id\": 961747,\n+ \"login\": \"kszucs\",\n+ \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+ \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/kszucs\"\n+ }\n+ },\n+ \"issue\": {\n+ \"assignee\": null,\n+ \"assignees\": [],\n+ \"author_association\": \"MEMBER\",\n+ \"body\": \"\",\n+ \"closed_at\": null,\n+ \"comments\": 0,\n+ \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments\",\n+ \"created_at\": \"2019-04-05T11:22:15Z\",\n+ \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/events\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+ \"id\": 429706959,\n+ \"labels\": [],\n+ \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}\",\n+ \"locked\": false,\n+ \"milestone\": null,\n+ \"node_id\": \"MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy\",\n+ \"number\": 26,\n+ \"pull_request\": {\n+ \"diff_url\": \"https://github.com/ursa-labs/ursabot/pull/26.diff\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+ \"patch_url\": \"https://github.com/ursa-labs/ursabot/pull/26.patch\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/26\"\n+ },\n+ \"repository_url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+ \"state\": \"open\",\n+ \"title\": \"Unittests for GithubHook\",\n+ \"updated_at\": \"2019-04-05T11:26:56Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+ \"user\": {\n+ \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+ \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+ \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/kszucs\",\n+ \"id\": 961747,\n+ \"login\": \"kszucs\",\n+ \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+ \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/kszucs\"\n+ }\n+ },\n+ \"organization\": {\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+ \"description\": \"Innovation lab for open source data science tools, powered by Apache Arrow\",\n+ \"events_url\": \"https://api.github.com/orgs/ursa-labs/events\",\n+ \"hooks_url\": \"https://api.github.com/orgs/ursa-labs/hooks\",\n+ \"id\": 46514972,\n+ \"issues_url\": \"https://api.github.com/orgs/ursa-labs/issues\",\n+ \"login\": \"ursa-labs\",\n+ \"members_url\": \"https://api.github.com/orgs/ursa-labs/members{/member}\",\n+ \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+ \"public_members_url\": \"https://api.github.com/orgs/ursa-labs/public_members{/member}\",\n+ \"repos_url\": \"https://api.github.com/orgs/ursa-labs/repos\",\n+ \"url\": \"https://api.github.com/orgs/ursa-labs\"\n+ },\n+ \"repository\": {\n+ \"archive_url\": \"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\",\n+ \"archived\": false,\n+ \"assignees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\",\n+ \"blobs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\",\n+ \"branches_url\": \"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\",\n+ \"clone_url\": \"https://github.com/ursa-labs/ursabot.git\",\n+ \"collaborators_url\": \"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\",\n+ \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\",\n+ \"commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\",\n+ \"compare_url\": \"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\",\n+ \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\",\n+ \"contributors_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contributors\",\n+ \"created_at\": \"2019-02-04T15:40:31Z\",\n+ \"default_branch\": \"master\",\n+ \"deployments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/deployments\",\n+ \"description\": null,\n+ \"disabled\": false,\n+ \"downloads_url\": \"https://api.github.com/repos/ursa-labs/ursabot/downloads\",\n+ \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/events\",\n+ \"fork\": false,\n+ \"forks\": 0,\n+ \"forks_count\": 0,\n+ \"forks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/forks\",\n+ \"full_name\": \"ursa-labs/ursabot\",\n+ \"git_commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\",\n+ \"git_refs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\",\n+ \"git_tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\",\n+ \"git_url\": \"git://github.com/ursa-labs/ursabot.git\",\n+ \"has_downloads\": true,\n+ \"has_issues\": true,\n+ \"has_pages\": false,\n+ \"has_projects\": true,\n+ \"has_wiki\": true,\n+ \"homepage\": null,\n+ \"hooks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/hooks\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot\",\n+ \"id\": 169101701,\n+ \"issue_comment_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\",\n+ \"issue_events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\",\n+ \"issues_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\",\n+ \"keys_url\": \"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\",\n+ \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\",\n+ \"language\": \"Jupyter Notebook\",\n+ \"languages_url\": \"https://api.github.com/repos/ursa-labs/ursabot/languages\",\n+ \"license\": null,\n+ \"merges_url\": \"https://api.github.com/repos/ursa-labs/ursabot/merges\",\n+ \"milestones_url\": \"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\",\n+ \"mirror_url\": null,\n+ \"name\": \"ursabot\",\n+ \"node_id\": \"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\",\n+ \"notifications_url\": \"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\",\n+ \"open_issues\": 19,\n+ \"open_issues_count\": 19,\n+ \"owner\": {\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+ \"events_url\": \"https://api.github.com/users/ursa-labs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/ursa-labs/followers\",\n+ \"following_url\": \"https://api.github.com/users/ursa-labs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/ursa-labs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/ursa-labs\",\n+ \"id\": 46514972,\n+ \"login\": \"ursa-labs\",\n+ \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+ \"organizations_url\": \"https://api.github.com/users/ursa-labs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/ursa-labs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/ursa-labs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/ursa-labs/subscriptions\",\n+ \"type\": \"Organization\",\n+ \"url\": \"https://api.github.com/users/ursa-labs\"\n+ },\n+ \"private\": false,\n+ \"pulls_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\",\n+ \"pushed_at\": \"2019-04-05T11:22:16Z\",\n+ \"releases_url\": \"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\",\n+ \"size\": 892,\n+ \"ssh_url\": \"git@github.com:ursa-labs/ursabot.git\",\n+ \"stargazers_count\": 1,\n+ \"stargazers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/stargazers\",\n+ \"statuses_url\": \"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\",\n+ \"subscribers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscribers\",\n+ \"subscription_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscription\",\n+ \"svn_url\": \"https://github.com/ursa-labs/ursabot\",\n+ \"tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/tags\",\n+ \"teams_url\": \"https://api.github.com/repos/ursa-labs/ursabot/teams\",\n+ \"trees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\",\n+ \"updated_at\": \"2019-04-04T17:49:10Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+ \"watchers\": 1,\n+ \"watchers_count\": 1\n+ },\n+ \"sender\": {\n+ \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+ \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+ \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/kszucs\",\n+ \"id\": 961747,\n+ \"login\": \"kszucs\",\n+ \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+ \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/kszucs\"\n+ }\n+}" - }, - { - "sha": "2770e29ba9086394455315e590c0b433d08e437e", - "filename": "ursabot/tests/fixtures/issue-comment-with-empty-command.json", - "status": "added", - "additions": 212, - "deletions": 0, - "changes": 212, - "blob_url": "https://github.com/ursa-labs/ursabot/blob/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-with-empty-command.json", - "raw_url": "https://github.com/ursa-labs/ursabot/raw/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-with-empty-command.json", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-with-empty-command.json?ref=2705da2b616b98fa6010a25813c5a7a27456f71d", - "patch": "@@ -0,0 +1,212 @@\n+{\n+ \"action\": \"created\",\n+ \"comment\": {\n+ \"author_association\": \"MEMBER\",\n+ \"body\": \"@ursabot \",\n+ \"created_at\": \"2019-04-05T11:35:46Z\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480243811\",\n+ \"id\": 480243811,\n+ \"issue_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+ \"node_id\": \"MDEyOklzc3VlQ29tbWVudDQ4MDI0MzgxMQ==\",\n+ \"updated_at\": \"2019-04-05T11:35:46Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480243811\",\n+ \"user\": {\n+ \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+ \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+ \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/kszucs\",\n+ \"id\": 961747,\n+ \"login\": \"kszucs\",\n+ \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+ \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/kszucs\"\n+ }\n+ },\n+ \"issue\": {\n+ \"assignee\": null,\n+ \"assignees\": [],\n+ \"author_association\": \"MEMBER\",\n+ \"body\": \"\",\n+ \"closed_at\": null,\n+ \"comments\": 1,\n+ \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments\",\n+ \"created_at\": \"2019-04-05T11:22:15Z\",\n+ \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/events\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+ \"id\": 429706959,\n+ \"labels\": [],\n+ \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}\",\n+ \"locked\": false,\n+ \"milestone\": null,\n+ \"node_id\": \"MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy\",\n+ \"number\": 26,\n+ \"pull_request\": {\n+ \"diff_url\": \"https://github.com/ursa-labs/ursabot/pull/26.diff\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+ \"patch_url\": \"https://github.com/ursa-labs/ursabot/pull/26.patch\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/26\"\n+ },\n+ \"repository_url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+ \"state\": \"open\",\n+ \"title\": \"Unittests for GithubHook\",\n+ \"updated_at\": \"2019-04-05T11:35:46Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+ \"user\": {\n+ \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+ \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+ \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/kszucs\",\n+ \"id\": 961747,\n+ \"login\": \"kszucs\",\n+ \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+ \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/kszucs\"\n+ }\n+ },\n+ \"organization\": {\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+ \"description\": \"Innovation lab for open source data science tools, powered by Apache Arrow\",\n+ \"events_url\": \"https://api.github.com/orgs/ursa-labs/events\",\n+ \"hooks_url\": \"https://api.github.com/orgs/ursa-labs/hooks\",\n+ \"id\": 46514972,\n+ \"issues_url\": \"https://api.github.com/orgs/ursa-labs/issues\",\n+ \"login\": \"ursa-labs\",\n+ \"members_url\": \"https://api.github.com/orgs/ursa-labs/members{/member}\",\n+ \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+ \"public_members_url\": \"https://api.github.com/orgs/ursa-labs/public_members{/member}\",\n+ \"repos_url\": \"https://api.github.com/orgs/ursa-labs/repos\",\n+ \"url\": \"https://api.github.com/orgs/ursa-labs\"\n+ },\n+ \"repository\": {\n+ \"archive_url\": \"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\",\n+ \"archived\": false,\n+ \"assignees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\",\n+ \"blobs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\",\n+ \"branches_url\": \"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\",\n+ \"clone_url\": \"https://github.com/ursa-labs/ursabot.git\",\n+ \"collaborators_url\": \"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\",\n+ \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\",\n+ \"commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\",\n+ \"compare_url\": \"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\",\n+ \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\",\n+ \"contributors_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contributors\",\n+ \"created_at\": \"2019-02-04T15:40:31Z\",\n+ \"default_branch\": \"master\",\n+ \"deployments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/deployments\",\n+ \"description\": null,\n+ \"disabled\": false,\n+ \"downloads_url\": \"https://api.github.com/repos/ursa-labs/ursabot/downloads\",\n+ \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/events\",\n+ \"fork\": false,\n+ \"forks\": 0,\n+ \"forks_count\": 0,\n+ \"forks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/forks\",\n+ \"full_name\": \"ursa-labs/ursabot\",\n+ \"git_commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\",\n+ \"git_refs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\",\n+ \"git_tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\",\n+ \"git_url\": \"git://github.com/ursa-labs/ursabot.git\",\n+ \"has_downloads\": true,\n+ \"has_issues\": true,\n+ \"has_pages\": false,\n+ \"has_projects\": true,\n+ \"has_wiki\": true,\n+ \"homepage\": null,\n+ \"hooks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/hooks\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot\",\n+ \"id\": 169101701,\n+ \"issue_comment_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\",\n+ \"issue_events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\",\n+ \"issues_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\",\n+ \"keys_url\": \"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\",\n+ \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\",\n+ \"language\": \"Jupyter Notebook\",\n+ \"languages_url\": \"https://api.github.com/repos/ursa-labs/ursabot/languages\",\n+ \"license\": null,\n+ \"merges_url\": \"https://api.github.com/repos/ursa-labs/ursabot/merges\",\n+ \"milestones_url\": \"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\",\n+ \"mirror_url\": null,\n+ \"name\": \"ursabot\",\n+ \"node_id\": \"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\",\n+ \"notifications_url\": \"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\",\n+ \"open_issues\": 19,\n+ \"open_issues_count\": 19,\n+ \"owner\": {\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+ \"events_url\": \"https://api.github.com/users/ursa-labs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/ursa-labs/followers\",\n+ \"following_url\": \"https://api.github.com/users/ursa-labs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/ursa-labs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/ursa-labs\",\n+ \"id\": 46514972,\n+ \"login\": \"ursa-labs\",\n+ \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+ \"organizations_url\": \"https://api.github.com/users/ursa-labs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/ursa-labs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/ursa-labs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/ursa-labs/subscriptions\",\n+ \"type\": \"Organization\",\n+ \"url\": \"https://api.github.com/users/ursa-labs\"\n+ },\n+ \"private\": false,\n+ \"pulls_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\",\n+ \"pushed_at\": \"2019-04-05T11:22:16Z\",\n+ \"releases_url\": \"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\",\n+ \"size\": 892,\n+ \"ssh_url\": \"git@github.com:ursa-labs/ursabot.git\",\n+ \"stargazers_count\": 1,\n+ \"stargazers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/stargazers\",\n+ \"statuses_url\": \"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\",\n+ \"subscribers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscribers\",\n+ \"subscription_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscription\",\n+ \"svn_url\": \"https://github.com/ursa-labs/ursabot\",\n+ \"tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/tags\",\n+ \"teams_url\": \"https://api.github.com/repos/ursa-labs/ursabot/teams\",\n+ \"trees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\",\n+ \"updated_at\": \"2019-04-04T17:49:10Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+ \"watchers\": 1,\n+ \"watchers_count\": 1\n+ },\n+ \"sender\": {\n+ \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+ \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+ \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/kszucs\",\n+ \"id\": 961747,\n+ \"login\": \"kszucs\",\n+ \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+ \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/kszucs\"\n+ }\n+}" - }, - { - "sha": "80ff46510a2f39ae60f7c3a98e5fdaef8e688784", - "filename": "ursabot/tests/fixtures/issue-comment-without-pull-request.json", - "status": "added", - "additions": 206, - "deletions": 0, - "changes": 206, - "blob_url": "https://github.com/ursa-labs/ursabot/blob/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-without-pull-request.json", - "raw_url": "https://github.com/ursa-labs/ursabot/raw/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-without-pull-request.json", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-without-pull-request.json?ref=2705da2b616b98fa6010a25813c5a7a27456f71d", - "patch": "@@ -0,0 +1,206 @@\n+{\n+ \"action\": \"created\",\n+ \"comment\": {\n+ \"author_association\": \"NONE\",\n+ \"body\": \"Ursabot only listens to pull request comments!\",\n+ \"created_at\": \"2019-04-05T11:53:43Z\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/issues/19#issuecomment-480248217\",\n+ \"id\": 480248217,\n+ \"issue_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/19\",\n+ \"node_id\": \"MDEyOklzc3VlQ29tbWVudDQ4MDI0ODIxNw==\",\n+ \"updated_at\": \"2019-04-05T11:53:43Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480248217\",\n+ \"user\": {\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/49275095?v=4\",\n+ \"events_url\": \"https://api.github.com/users/ursabot/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/ursabot/followers\",\n+ \"following_url\": \"https://api.github.com/users/ursabot/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/ursabot/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/ursabot\",\n+ \"id\": 49275095,\n+ \"login\": \"ursabot\",\n+ \"node_id\": \"MDQ6VXNlcjQ5Mjc1MDk1\",\n+ \"organizations_url\": \"https://api.github.com/users/ursabot/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/ursabot/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/ursabot/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/ursabot/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/ursabot/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/ursabot\"\n+ }\n+ },\n+ \"issue\": {\n+ \"assignee\": null,\n+ \"assignees\": [],\n+ \"author_association\": \"MEMBER\",\n+ \"body\": \"\",\n+ \"closed_at\": null,\n+ \"comments\": 4,\n+ \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/19/comments\",\n+ \"created_at\": \"2019-04-02T09:56:41Z\",\n+ \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/19/events\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/issues/19\",\n+ \"id\": 428131685,\n+ \"labels\": [],\n+ \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/19/labels{/name}\",\n+ \"locked\": false,\n+ \"milestone\": null,\n+ \"node_id\": \"MDU6SXNzdWU0MjgxMzE2ODU=\",\n+ \"number\": 19,\n+ \"repository_url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+ \"state\": \"open\",\n+ \"title\": \"Build ursabot itself via ursabot\",\n+ \"updated_at\": \"2019-04-05T11:53:43Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/19\",\n+ \"user\": {\n+ \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+ \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+ \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/kszucs\",\n+ \"id\": 961747,\n+ \"login\": \"kszucs\",\n+ \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+ \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/kszucs\"\n+ }\n+ },\n+ \"organization\": {\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+ \"description\": \"Innovation lab for open source data science tools, powered by Apache Arrow\",\n+ \"events_url\": \"https://api.github.com/orgs/ursa-labs/events\",\n+ \"hooks_url\": \"https://api.github.com/orgs/ursa-labs/hooks\",\n+ \"id\": 46514972,\n+ \"issues_url\": \"https://api.github.com/orgs/ursa-labs/issues\",\n+ \"login\": \"ursa-labs\",\n+ \"members_url\": \"https://api.github.com/orgs/ursa-labs/members{/member}\",\n+ \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+ \"public_members_url\": \"https://api.github.com/orgs/ursa-labs/public_members{/member}\",\n+ \"repos_url\": \"https://api.github.com/orgs/ursa-labs/repos\",\n+ \"url\": \"https://api.github.com/orgs/ursa-labs\"\n+ },\n+ \"repository\": {\n+ \"archive_url\": \"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\",\n+ \"archived\": false,\n+ \"assignees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\",\n+ \"blobs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\",\n+ \"branches_url\": \"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\",\n+ \"clone_url\": \"https://github.com/ursa-labs/ursabot.git\",\n+ \"collaborators_url\": \"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\",\n+ \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\",\n+ \"commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\",\n+ \"compare_url\": \"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\",\n+ \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\",\n+ \"contributors_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contributors\",\n+ \"created_at\": \"2019-02-04T15:40:31Z\",\n+ \"default_branch\": \"master\",\n+ \"deployments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/deployments\",\n+ \"description\": null,\n+ \"disabled\": false,\n+ \"downloads_url\": \"https://api.github.com/repos/ursa-labs/ursabot/downloads\",\n+ \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/events\",\n+ \"fork\": false,\n+ \"forks\": 0,\n+ \"forks_count\": 0,\n+ \"forks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/forks\",\n+ \"full_name\": \"ursa-labs/ursabot\",\n+ \"git_commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\",\n+ \"git_refs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\",\n+ \"git_tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\",\n+ \"git_url\": \"git://github.com/ursa-labs/ursabot.git\",\n+ \"has_downloads\": true,\n+ \"has_issues\": true,\n+ \"has_pages\": false,\n+ \"has_projects\": true,\n+ \"has_wiki\": true,\n+ \"homepage\": null,\n+ \"hooks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/hooks\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot\",\n+ \"id\": 169101701,\n+ \"issue_comment_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\",\n+ \"issue_events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\",\n+ \"issues_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\",\n+ \"keys_url\": \"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\",\n+ \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\",\n+ \"language\": \"Jupyter Notebook\",\n+ \"languages_url\": \"https://api.github.com/repos/ursa-labs/ursabot/languages\",\n+ \"license\": null,\n+ \"merges_url\": \"https://api.github.com/repos/ursa-labs/ursabot/merges\",\n+ \"milestones_url\": \"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\",\n+ \"mirror_url\": null,\n+ \"name\": \"ursabot\",\n+ \"node_id\": \"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\",\n+ \"notifications_url\": \"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\",\n+ \"open_issues\": 19,\n+ \"open_issues_count\": 19,\n+ \"owner\": {\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+ \"events_url\": \"https://api.github.com/users/ursa-labs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/ursa-labs/followers\",\n+ \"following_url\": \"https://api.github.com/users/ursa-labs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/ursa-labs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/ursa-labs\",\n+ \"id\": 46514972,\n+ \"login\": \"ursa-labs\",\n+ \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+ \"organizations_url\": \"https://api.github.com/users/ursa-labs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/ursa-labs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/ursa-labs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/ursa-labs/subscriptions\",\n+ \"type\": \"Organization\",\n+ \"url\": \"https://api.github.com/users/ursa-labs\"\n+ },\n+ \"private\": false,\n+ \"pulls_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\",\n+ \"pushed_at\": \"2019-04-05T11:22:16Z\",\n+ \"releases_url\": \"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\",\n+ \"size\": 892,\n+ \"ssh_url\": \"git@github.com:ursa-labs/ursabot.git\",\n+ \"stargazers_count\": 1,\n+ \"stargazers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/stargazers\",\n+ \"statuses_url\": \"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\",\n+ \"subscribers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscribers\",\n+ \"subscription_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscription\",\n+ \"svn_url\": \"https://github.com/ursa-labs/ursabot\",\n+ \"tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/tags\",\n+ \"teams_url\": \"https://api.github.com/repos/ursa-labs/ursabot/teams\",\n+ \"trees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\",\n+ \"updated_at\": \"2019-04-04T17:49:10Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+ \"watchers\": 1,\n+ \"watchers_count\": 1\n+ },\n+ \"sender\": {\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/49275095?v=4\",\n+ \"events_url\": \"https://api.github.com/users/ursabot/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/ursabot/followers\",\n+ \"following_url\": \"https://api.github.com/users/ursabot/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/ursabot/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/ursabot\",\n+ \"id\": 49275095,\n+ \"login\": \"ursabot\",\n+ \"node_id\": \"MDQ6VXNlcjQ5Mjc1MDk1\",\n+ \"organizations_url\": \"https://api.github.com/users/ursabot/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/ursabot/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/ursabot/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/ursabot/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/ursabot/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/ursabot\"\n+ }\n+}" - }, - { - "sha": "c738bb0eb54c87ba0f23e97e827d77c2be74d0b6", - "filename": "ursabot/tests/test_hooks.py", - "status": "modified", - "additions": 4, - "deletions": 4, - "changes": 8, - "blob_url": "https://github.com/ursa-labs/ursabot/blob/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/test_hooks.py", - "raw_url": "https://github.com/ursa-labs/ursabot/raw/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/test_hooks.py", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/test_hooks.py?ref=2705da2b616b98fa6010a25813c5a7a27456f71d", - "patch": "@@ -54,7 +54,7 @@ class TestGithubHook(ChangeHookTestCase):\n await self.request('ping', {})\n assert len(self.hook.master.data.updates.changesAdded) == 0\n \n- @ensure_deferred\n- async def test_issue_comment(self):\n- payload = {}\n- await self.request('issue_comment', payload)\n+ # @ensure_deferred\n+ # async def test_issue_comment(self):\n+ # payload = {}\n+ # await self.request('issue_comment', payload)" - } - ] -} \ No newline at end of file diff --git a/dev/archery/archery/tests/fixtures/pull-request-26-files.json b/dev/archery/archery/tests/fixtures/pull-request-26-files.json deleted file mode 100644 index b039b3d10539..000000000000 --- a/dev/archery/archery/tests/fixtures/pull-request-26-files.json +++ /dev/null @@ -1,170 +0,0 @@ -[ - { - "sha": "ebfe3f6c5e98723f9751c99ce8ce798f1ba529c5", - "filename": ".travis.yml", - "status": "modified", - "additions": 4, - "deletions": 1, - "changes": 5, - "blob_url": "https://github.com/ursa-labs/ursabot/blob/70267dee34884e4b972388e1b30d57f6248c58d0/.travis.yml", - "raw_url": "https://github.com/ursa-labs/ursabot/raw/70267dee34884e4b972388e1b30d57f6248c58d0/.travis.yml", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/.travis.yml?ref=70267dee34884e4b972388e1b30d57f6248c58d0", - "patch": "@@ -4,7 +4,10 @@ services:\n python:\n - 3.6\n script:\n- - pip install \"pytest>=3.9\" flake8 -e .\n+ # --no-binary buildbot is required because buildbot doesn't bundle its tests\n+ # to binary wheels, but ursabot's test suite depends on buildbot's so install\n+ # it from source\n+ - pip install --no-binary buildbot \"pytest>=3.9\" mock flake8 -e .\n \n # run linter\n - flake8 ursabot" - }, - { - "sha": "86ad809d3f74c175b92ac58c6c645b0fbf5fa2c5", - "filename": "setup.py", - "status": "modified", - "additions": 6, - "deletions": 1, - "changes": 7, - "blob_url": "https://github.com/ursa-labs/ursabot/blob/70267dee34884e4b972388e1b30d57f6248c58d0/setup.py", - "raw_url": "https://github.com/ursa-labs/ursabot/raw/70267dee34884e4b972388e1b30d57f6248c58d0/setup.py", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/setup.py?ref=70267dee34884e4b972388e1b30d57f6248c58d0", - "patch": "@@ -1,8 +1,13 @@\n #!/usr/bin/env python\n \n+import sys\n from setuptools import setup\n \n \n+if sys.version_info < (3, 6):\n+ sys.exit('Python < 3.6 is not supported due to missing asyncio support')\n+\n+\n # TODO(kszucs): add package data, change maintainer\n setup(\n name='ursabot',\n@@ -15,7 +20,7 @@\n setup_requires=['setuptools_scm'],\n install_requires=['click', 'dask', 'docker', 'docker-map', 'toolz',\n 'buildbot', 'treq'],\n- tests_require=['pytest>=3.9'],\n+ tests_require=['pytest>=3.9', 'mock'],\n entry_points='''\n [console_scripts]\n ursabot=ursabot.cli:ursabot" - }, - { - "sha": "c884f3f85bba499d77d9ad28bcd0ff5edf80f957", - "filename": "ursabot/factories.py", - "status": "modified", - "additions": 6, - "deletions": 2, - "changes": 8, - "blob_url": "https://github.com/ursa-labs/ursabot/blob/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/factories.py", - "raw_url": "https://github.com/ursa-labs/ursabot/raw/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/factories.py", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/factories.py?ref=70267dee34884e4b972388e1b30d57f6248c58d0", - "patch": "@@ -79,8 +79,12 @@ def prepend_step(self, step):\n repourl='https://github.com/ursa-labs/ursabot',\n mode='full'),\n ShellCommand(command=['ls', '-lah']),\n- ShellCommand(command=['pip', 'install', 'pytest', 'flake8']),\n- ShellCommand(command=['pip', 'install', '-e', '.']),\n+ ShellCommand(command=['pip', 'install', 'pytest', 'flake8', 'mock']),\n+ # --no-binary buildbot is required because buildbot doesn't bundle its\n+ # tests to binary wheels, but ursabot's test suite depends on buildbot's\n+ # so install it from source\n+ ShellCommand(command=['pip', 'install', '--no-binary', 'buildbot',\n+ '-e', '.']),\n ShellCommand(command=['flake8']),\n ShellCommand(command=['pytest', '-v', '-m', 'not docker', 'ursabot']),\n ShellCommand(command=['buildbot', 'checkconfig', '.'])" - }, - { - "sha": "0265cfbd9c2882f492469882a7bf513a1c1b5af4", - "filename": "ursabot/hooks.py", - "status": "modified", - "additions": 17, - "deletions": 19, - "changes": 36, - "blob_url": "https://github.com/ursa-labs/ursabot/blob/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/hooks.py", - "raw_url": "https://github.com/ursa-labs/ursabot/raw/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/hooks.py", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/hooks.py?ref=70267dee34884e4b972388e1b30d57f6248c58d0", - "patch": "@@ -1,11 +1,11 @@\n from urllib.parse import urlparse\n \n from twisted.python import log\n-from twisted.internet import defer\n \n from buildbot.www.hooks.github import GitHubEventHandler\n from buildbot.util.httpclientservice import HTTPClientService\n \n+from .utils import ensure_deferred\n \n BOTNAME = 'ursabot'\n \n@@ -22,20 +22,18 @@ def _client(self):\n self.master, self.github_api_endpoint, headers=headers,\n debug=self.debug, verify=self.verify)\n \n- @defer.inlineCallbacks\n- def _get(self, url):\n+ async def _get(self, url):\n url = urlparse(url)\n- client = yield self._client()\n- response = yield client.get(url.path)\n- result = yield response.json()\n+ client = await self._client()\n+ response = await client.get(url.path)\n+ result = await response.json()\n return result\n \n- @defer.inlineCallbacks\n- def _post(self, url, data):\n+ async def _post(self, url, data):\n url = urlparse(url)\n- client = yield self._client()\n- response = yield client.post(url.path, json=data)\n- result = yield response.json()\n+ client = await self._client()\n+ response = await client.post(url.path, json=data)\n+ result = await response.json()\n log.msg(f'POST to {url} with the following result: {result}')\n return result\n \n@@ -46,8 +44,8 @@ def _parse_command(self, message):\n return message.split(mention)[-1].lower().strip()\n return None\n \n- @defer.inlineCallbacks\n- def handle_issue_comment(self, payload, event):\n+ @ensure_deferred\n+ async def handle_issue_comment(self, payload, event):\n issue = payload['issue']\n comments_url = issue['comments_url']\n command = self._parse_command(payload['comment']['body'])\n@@ -64,16 +62,16 @@ def handle_issue_comment(self, payload, event):\n elif command == 'build':\n if 'pull_request' not in issue:\n message = 'Ursabot only listens to pull request comments!'\n- yield self._post(comments_url, {'body': message})\n+ await self._post(comments_url, {'body': message})\n return [], 'git'\n else:\n message = f'Unknown command \"{command}\"'\n- yield self._post(comments_url, {'body': message})\n+ await self._post(comments_url, {'body': message})\n return [], 'git'\n \n try:\n- pull_request = yield self._get(issue['pull_request']['url'])\n- changes, _ = yield self.handle_pull_request({\n+ pull_request = await self._get(issue['pull_request']['url'])\n+ changes, _ = await self.handle_pull_request({\n 'action': 'synchronize',\n 'sender': payload['sender'],\n 'repository': payload['repository'],\n@@ -82,11 +80,11 @@ def handle_issue_comment(self, payload, event):\n }, event)\n except Exception as e:\n message = \"I've failed to start builds for this PR\"\n- yield self._post(comments_url, {'body': message})\n+ await self._post(comments_url, {'body': message})\n raise e\n else:\n message = \"I've successfully started builds for this PR\"\n- yield self._post(comments_url, {'body': message})\n+ await self._post(comments_url, {'body': message})\n return changes, 'git'\n \n # TODO(kszucs):" - }, - { - "sha": "1e1ecf2ce47da929dbf1b93632640e7e6ae1cfe0", - "filename": "ursabot/steps.py", - "status": "modified", - "additions": 13, - "deletions": 13, - "changes": 26, - "blob_url": "https://github.com/ursa-labs/ursabot/blob/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/steps.py", - "raw_url": "https://github.com/ursa-labs/ursabot/raw/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/steps.py", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/steps.py?ref=70267dee34884e4b972388e1b30d57f6248c58d0", - "patch": "@@ -1,9 +1,9 @@\n-from twisted.internet import defer\n-\n from buildbot.plugins import steps, util\n from buildbot.process import buildstep\n from buildbot.process.results import SUCCESS\n \n+from .utils import ensure_deferred\n+\n \n class ShellMixin(buildstep.ShellMixin):\n \"\"\"Run command in a login bash shell\n@@ -49,10 +49,10 @@ def __init__(self, **kwargs):\n kwargs = self.setupShellMixin(kwargs)\n super().__init__(**kwargs)\n \n- @defer.inlineCallbacks\n- def run(self):\n- cmd = yield self.makeRemoteShellCommand(command=self.command)\n- yield self.runCommand(cmd)\n+ @ensure_deferred\n+ async def run(self):\n+ cmd = await self.makeRemoteShellCommand(command=self.command)\n+ await self.runCommand(cmd)\n return cmd.results()\n \n \n@@ -71,8 +71,8 @@ class CMake(ShellMixin, steps.CMake):\n \n name = 'CMake'\n \n- @defer.inlineCallbacks\n- def run(self):\n+ @ensure_deferred\n+ async def run(self):\n \"\"\"Create and run CMake command\n \n Copied from the original CMake implementation to handle None values as\n@@ -94,8 +94,8 @@ def run(self):\n if self.options is not None:\n command.extend(self.options)\n \n- cmd = yield self.makeRemoteShellCommand(command=command)\n- yield self.runCommand(cmd)\n+ cmd = await self.makeRemoteShellCommand(command=command)\n+ await self.runCommand(cmd)\n \n return cmd.results()\n \n@@ -117,8 +117,8 @@ def __init__(self, variables, source='WorkerEnvironment', **kwargs):\n self.source = source\n super().__init__(**kwargs)\n \n- @defer.inlineCallbacks\n- def run(self):\n+ @ensure_deferred\n+ async def run(self):\n # on Windows, environment variables are case-insensitive, but we have\n # a case-sensitive dictionary in worker_environ. Fortunately, that\n # dictionary is also folded to uppercase, so we can simply fold the\n@@ -139,7 +139,7 @@ def run(self):\n # TODO(kszucs) try with self.setProperty similarly like in\n # SetProperties\n properties.setProperty(prop, value, self.source, runtime=True)\n- yield self.addCompleteLog('set-prop', f'{prop}: {value}')\n+ await self.addCompleteLog('set-prop', f'{prop}: {value}')\n \n return SUCCESS\n " - }, - { - "sha": "6a7d5308be6608f542a810d410f9240157a1340f", - "filename": "ursabot/tests/fixtures/issue-comment-build-command.json", - "status": "added", - "additions": 212, - "deletions": 0, - "changes": 212, - "blob_url": "https://github.com/ursa-labs/ursabot/blob/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/fixtures/issue-comment-build-command.json", - "raw_url": "https://github.com/ursa-labs/ursabot/raw/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/fixtures/issue-comment-build-command.json", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-build-command.json?ref=70267dee34884e4b972388e1b30d57f6248c58d0", - "patch": "@@ -0,0 +1,212 @@\n+{\n+ \"action\": \"created\",\n+ \"comment\": {\n+ \"author_association\": \"MEMBER\",\n+ \"body\": \"@ursabot build\",\n+ \"created_at\": \"2019-04-05T11:55:43Z\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480248726\",\n+ \"id\": 480248726,\n+ \"issue_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+ \"node_id\": \"MDEyOklzc3VlQ29tbWVudDQ4MDI0ODcyNg==\",\n+ \"updated_at\": \"2019-04-05T11:55:43Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480248726\",\n+ \"user\": {\n+ \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+ \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+ \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/kszucs\",\n+ \"id\": 961747,\n+ \"login\": \"kszucs\",\n+ \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+ \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/kszucs\"\n+ }\n+ },\n+ \"issue\": {\n+ \"assignee\": null,\n+ \"assignees\": [],\n+ \"author_association\": \"MEMBER\",\n+ \"body\": \"\",\n+ \"closed_at\": null,\n+ \"comments\": 3,\n+ \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments\",\n+ \"created_at\": \"2019-04-05T11:22:15Z\",\n+ \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/events\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+ \"id\": 429706959,\n+ \"labels\": [],\n+ \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}\",\n+ \"locked\": false,\n+ \"milestone\": null,\n+ \"node_id\": \"MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy\",\n+ \"number\": 26,\n+ \"pull_request\": {\n+ \"diff_url\": \"https://github.com/ursa-labs/ursabot/pull/26.diff\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+ \"patch_url\": \"https://github.com/ursa-labs/ursabot/pull/26.patch\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/26\"\n+ },\n+ \"repository_url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+ \"state\": \"open\",\n+ \"title\": \"Unittests for GithubHook\",\n+ \"updated_at\": \"2019-04-05T11:55:43Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+ \"user\": {\n+ \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+ \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+ \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/kszucs\",\n+ \"id\": 961747,\n+ \"login\": \"kszucs\",\n+ \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+ \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/kszucs\"\n+ }\n+ },\n+ \"organization\": {\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+ \"description\": \"Innovation lab for open source data science tools, powered by Apache Arrow\",\n+ \"events_url\": \"https://api.github.com/orgs/ursa-labs/events\",\n+ \"hooks_url\": \"https://api.github.com/orgs/ursa-labs/hooks\",\n+ \"id\": 46514972,\n+ \"issues_url\": \"https://api.github.com/orgs/ursa-labs/issues\",\n+ \"login\": \"ursa-labs\",\n+ \"members_url\": \"https://api.github.com/orgs/ursa-labs/members{/member}\",\n+ \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+ \"public_members_url\": \"https://api.github.com/orgs/ursa-labs/public_members{/member}\",\n+ \"repos_url\": \"https://api.github.com/orgs/ursa-labs/repos\",\n+ \"url\": \"https://api.github.com/orgs/ursa-labs\"\n+ },\n+ \"repository\": {\n+ \"archive_url\": \"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\",\n+ \"archived\": false,\n+ \"assignees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\",\n+ \"blobs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\",\n+ \"branches_url\": \"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\",\n+ \"clone_url\": \"https://github.com/ursa-labs/ursabot.git\",\n+ \"collaborators_url\": \"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\",\n+ \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\",\n+ \"commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\",\n+ \"compare_url\": \"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\",\n+ \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\",\n+ \"contributors_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contributors\",\n+ \"created_at\": \"2019-02-04T15:40:31Z\",\n+ \"default_branch\": \"master\",\n+ \"deployments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/deployments\",\n+ \"description\": null,\n+ \"disabled\": false,\n+ \"downloads_url\": \"https://api.github.com/repos/ursa-labs/ursabot/downloads\",\n+ \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/events\",\n+ \"fork\": false,\n+ \"forks\": 0,\n+ \"forks_count\": 0,\n+ \"forks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/forks\",\n+ \"full_name\": \"ursa-labs/ursabot\",\n+ \"git_commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\",\n+ \"git_refs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\",\n+ \"git_tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\",\n+ \"git_url\": \"git://github.com/ursa-labs/ursabot.git\",\n+ \"has_downloads\": true,\n+ \"has_issues\": true,\n+ \"has_pages\": false,\n+ \"has_projects\": true,\n+ \"has_wiki\": true,\n+ \"homepage\": null,\n+ \"hooks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/hooks\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot\",\n+ \"id\": 169101701,\n+ \"issue_comment_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\",\n+ \"issue_events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\",\n+ \"issues_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\",\n+ \"keys_url\": \"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\",\n+ \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\",\n+ \"language\": \"Jupyter Notebook\",\n+ \"languages_url\": \"https://api.github.com/repos/ursa-labs/ursabot/languages\",\n+ \"license\": null,\n+ \"merges_url\": \"https://api.github.com/repos/ursa-labs/ursabot/merges\",\n+ \"milestones_url\": \"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\",\n+ \"mirror_url\": null,\n+ \"name\": \"ursabot\",\n+ \"node_id\": \"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\",\n+ \"notifications_url\": \"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\",\n+ \"open_issues\": 19,\n+ \"open_issues_count\": 19,\n+ \"owner\": {\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+ \"events_url\": \"https://api.github.com/users/ursa-labs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/ursa-labs/followers\",\n+ \"following_url\": \"https://api.github.com/users/ursa-labs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/ursa-labs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/ursa-labs\",\n+ \"id\": 46514972,\n+ \"login\": \"ursa-labs\",\n+ \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+ \"organizations_url\": \"https://api.github.com/users/ursa-labs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/ursa-labs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/ursa-labs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/ursa-labs/subscriptions\",\n+ \"type\": \"Organization\",\n+ \"url\": \"https://api.github.com/users/ursa-labs\"\n+ },\n+ \"private\": false,\n+ \"pulls_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\",\n+ \"pushed_at\": \"2019-04-05T11:22:16Z\",\n+ \"releases_url\": \"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\",\n+ \"size\": 892,\n+ \"ssh_url\": \"git@github.com:ursa-labs/ursabot.git\",\n+ \"stargazers_count\": 1,\n+ \"stargazers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/stargazers\",\n+ \"statuses_url\": \"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\",\n+ \"subscribers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscribers\",\n+ \"subscription_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscription\",\n+ \"svn_url\": \"https://github.com/ursa-labs/ursabot\",\n+ \"tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/tags\",\n+ \"teams_url\": \"https://api.github.com/repos/ursa-labs/ursabot/teams\",\n+ \"trees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\",\n+ \"updated_at\": \"2019-04-04T17:49:10Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+ \"watchers\": 1,\n+ \"watchers_count\": 1\n+ },\n+ \"sender\": {\n+ \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+ \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+ \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/kszucs\",\n+ \"id\": 961747,\n+ \"login\": \"kszucs\",\n+ \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+ \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/kszucs\"\n+ }\n+}" - }, - { - "sha": "7ef554e333327f0e62aa1fd76b4b17844a39adeb", - "filename": "ursabot/tests/fixtures/issue-comment-by-ursabot.json", - "status": "added", - "additions": 212, - "deletions": 0, - "changes": 212, - "blob_url": "https://github.com/ursa-labs/ursabot/blob/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/fixtures/issue-comment-by-ursabot.json", - "raw_url": "https://github.com/ursa-labs/ursabot/raw/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/fixtures/issue-comment-by-ursabot.json", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-by-ursabot.json?ref=70267dee34884e4b972388e1b30d57f6248c58d0", - "patch": "@@ -0,0 +1,212 @@\n+{\n+ \"action\": \"created\",\n+ \"comment\": {\n+ \"author_association\": \"NONE\",\n+ \"body\": \"Unknown command \\\"\\\"\",\n+ \"created_at\": \"2019-04-05T11:35:47Z\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480243815\",\n+ \"id\": 480243815,\n+ \"issue_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+ \"node_id\": \"MDEyOklzc3VlQ29tbWVudDQ4MDI0MzgxNQ==\",\n+ \"updated_at\": \"2019-04-05T11:35:47Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480243815\",\n+ \"user\": {\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/49275095?v=4\",\n+ \"events_url\": \"https://api.github.com/users/ursabot/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/ursabot/followers\",\n+ \"following_url\": \"https://api.github.com/users/ursabot/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/ursabot/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/ursabot\",\n+ \"id\": 49275095,\n+ \"login\": \"ursabot\",\n+ \"node_id\": \"MDQ6VXNlcjQ5Mjc1MDk1\",\n+ \"organizations_url\": \"https://api.github.com/users/ursabot/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/ursabot/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/ursabot/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/ursabot/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/ursabot/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/ursabot\"\n+ }\n+ },\n+ \"issue\": {\n+ \"assignee\": null,\n+ \"assignees\": [],\n+ \"author_association\": \"MEMBER\",\n+ \"body\": \"\",\n+ \"closed_at\": null,\n+ \"comments\": 2,\n+ \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments\",\n+ \"created_at\": \"2019-04-05T11:22:15Z\",\n+ \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/events\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+ \"id\": 429706959,\n+ \"labels\": [],\n+ \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}\",\n+ \"locked\": false,\n+ \"milestone\": null,\n+ \"node_id\": \"MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy\",\n+ \"number\": 26,\n+ \"pull_request\": {\n+ \"diff_url\": \"https://github.com/ursa-labs/ursabot/pull/26.diff\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+ \"patch_url\": \"https://github.com/ursa-labs/ursabot/pull/26.patch\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/26\"\n+ },\n+ \"repository_url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+ \"state\": \"open\",\n+ \"title\": \"Unittests for GithubHook\",\n+ \"updated_at\": \"2019-04-05T11:35:47Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+ \"user\": {\n+ \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+ \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+ \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/kszucs\",\n+ \"id\": 961747,\n+ \"login\": \"kszucs\",\n+ \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+ \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/kszucs\"\n+ }\n+ },\n+ \"organization\": {\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+ \"description\": \"Innovation lab for open source data science tools, powered by Apache Arrow\",\n+ \"events_url\": \"https://api.github.com/orgs/ursa-labs/events\",\n+ \"hooks_url\": \"https://api.github.com/orgs/ursa-labs/hooks\",\n+ \"id\": 46514972,\n+ \"issues_url\": \"https://api.github.com/orgs/ursa-labs/issues\",\n+ \"login\": \"ursa-labs\",\n+ \"members_url\": \"https://api.github.com/orgs/ursa-labs/members{/member}\",\n+ \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+ \"public_members_url\": \"https://api.github.com/orgs/ursa-labs/public_members{/member}\",\n+ \"repos_url\": \"https://api.github.com/orgs/ursa-labs/repos\",\n+ \"url\": \"https://api.github.com/orgs/ursa-labs\"\n+ },\n+ \"repository\": {\n+ \"archive_url\": \"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\",\n+ \"archived\": false,\n+ \"assignees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\",\n+ \"blobs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\",\n+ \"branches_url\": \"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\",\n+ \"clone_url\": \"https://github.com/ursa-labs/ursabot.git\",\n+ \"collaborators_url\": \"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\",\n+ \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\",\n+ \"commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\",\n+ \"compare_url\": \"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\",\n+ \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\",\n+ \"contributors_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contributors\",\n+ \"created_at\": \"2019-02-04T15:40:31Z\",\n+ \"default_branch\": \"master\",\n+ \"deployments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/deployments\",\n+ \"description\": null,\n+ \"disabled\": false,\n+ \"downloads_url\": \"https://api.github.com/repos/ursa-labs/ursabot/downloads\",\n+ \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/events\",\n+ \"fork\": false,\n+ \"forks\": 0,\n+ \"forks_count\": 0,\n+ \"forks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/forks\",\n+ \"full_name\": \"ursa-labs/ursabot\",\n+ \"git_commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\",\n+ \"git_refs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\",\n+ \"git_tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\",\n+ \"git_url\": \"git://github.com/ursa-labs/ursabot.git\",\n+ \"has_downloads\": true,\n+ \"has_issues\": true,\n+ \"has_pages\": false,\n+ \"has_projects\": true,\n+ \"has_wiki\": true,\n+ \"homepage\": null,\n+ \"hooks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/hooks\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot\",\n+ \"id\": 169101701,\n+ \"issue_comment_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\",\n+ \"issue_events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\",\n+ \"issues_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\",\n+ \"keys_url\": \"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\",\n+ \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\",\n+ \"language\": \"Jupyter Notebook\",\n+ \"languages_url\": \"https://api.github.com/repos/ursa-labs/ursabot/languages\",\n+ \"license\": null,\n+ \"merges_url\": \"https://api.github.com/repos/ursa-labs/ursabot/merges\",\n+ \"milestones_url\": \"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\",\n+ \"mirror_url\": null,\n+ \"name\": \"ursabot\",\n+ \"node_id\": \"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\",\n+ \"notifications_url\": \"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\",\n+ \"open_issues\": 19,\n+ \"open_issues_count\": 19,\n+ \"owner\": {\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+ \"events_url\": \"https://api.github.com/users/ursa-labs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/ursa-labs/followers\",\n+ \"following_url\": \"https://api.github.com/users/ursa-labs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/ursa-labs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/ursa-labs\",\n+ \"id\": 46514972,\n+ \"login\": \"ursa-labs\",\n+ \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+ \"organizations_url\": \"https://api.github.com/users/ursa-labs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/ursa-labs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/ursa-labs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/ursa-labs/subscriptions\",\n+ \"type\": \"Organization\",\n+ \"url\": \"https://api.github.com/users/ursa-labs\"\n+ },\n+ \"private\": false,\n+ \"pulls_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\",\n+ \"pushed_at\": \"2019-04-05T11:22:16Z\",\n+ \"releases_url\": \"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\",\n+ \"size\": 892,\n+ \"ssh_url\": \"git@github.com:ursa-labs/ursabot.git\",\n+ \"stargazers_count\": 1,\n+ \"stargazers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/stargazers\",\n+ \"statuses_url\": \"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\",\n+ \"subscribers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscribers\",\n+ \"subscription_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscription\",\n+ \"svn_url\": \"https://github.com/ursa-labs/ursabot\",\n+ \"tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/tags\",\n+ \"teams_url\": \"https://api.github.com/repos/ursa-labs/ursabot/teams\",\n+ \"trees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\",\n+ \"updated_at\": \"2019-04-04T17:49:10Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+ \"watchers\": 1,\n+ \"watchers_count\": 1\n+ },\n+ \"sender\": {\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/49275095?v=4\",\n+ \"events_url\": \"https://api.github.com/users/ursabot/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/ursabot/followers\",\n+ \"following_url\": \"https://api.github.com/users/ursabot/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/ursabot/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/ursabot\",\n+ \"id\": 49275095,\n+ \"login\": \"ursabot\",\n+ \"node_id\": \"MDQ6VXNlcjQ5Mjc1MDk1\",\n+ \"organizations_url\": \"https://api.github.com/users/ursabot/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/ursabot/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/ursabot/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/ursabot/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/ursabot/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/ursabot\"\n+ }\n+}" - }, - { - "sha": "a8082dbc91fdfe815b795e49ec10e49000771ef5", - "filename": "ursabot/tests/fixtures/issue-comment-not-mentioning-ursabot.json", - "status": "added", - "additions": 212, - "deletions": 0, - "changes": 212, - "blob_url": "https://github.com/ursa-labs/ursabot/blob/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/fixtures/issue-comment-not-mentioning-ursabot.json", - "raw_url": "https://github.com/ursa-labs/ursabot/raw/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/fixtures/issue-comment-not-mentioning-ursabot.json", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-not-mentioning-ursabot.json?ref=70267dee34884e4b972388e1b30d57f6248c58d0", - "patch": "@@ -0,0 +1,212 @@\n+{\n+ \"action\": \"created\",\n+ \"comment\": {\n+ \"author_association\": \"MEMBER\",\n+ \"body\": \"bear is no game\",\n+ \"created_at\": \"2019-04-05T11:26:56Z\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480241727\",\n+ \"id\": 480241727,\n+ \"issue_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+ \"node_id\": \"MDEyOklzc3VlQ29tbWVudDQ4MDI0MTcyNw==\",\n+ \"updated_at\": \"2019-04-05T11:26:56Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480241727\",\n+ \"user\": {\n+ \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+ \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+ \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/kszucs\",\n+ \"id\": 961747,\n+ \"login\": \"kszucs\",\n+ \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+ \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/kszucs\"\n+ }\n+ },\n+ \"issue\": {\n+ \"assignee\": null,\n+ \"assignees\": [],\n+ \"author_association\": \"MEMBER\",\n+ \"body\": \"\",\n+ \"closed_at\": null,\n+ \"comments\": 0,\n+ \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments\",\n+ \"created_at\": \"2019-04-05T11:22:15Z\",\n+ \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/events\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+ \"id\": 429706959,\n+ \"labels\": [],\n+ \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}\",\n+ \"locked\": false,\n+ \"milestone\": null,\n+ \"node_id\": \"MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy\",\n+ \"number\": 26,\n+ \"pull_request\": {\n+ \"diff_url\": \"https://github.com/ursa-labs/ursabot/pull/26.diff\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+ \"patch_url\": \"https://github.com/ursa-labs/ursabot/pull/26.patch\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/26\"\n+ },\n+ \"repository_url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+ \"state\": \"open\",\n+ \"title\": \"Unittests for GithubHook\",\n+ \"updated_at\": \"2019-04-05T11:26:56Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+ \"user\": {\n+ \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+ \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+ \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/kszucs\",\n+ \"id\": 961747,\n+ \"login\": \"kszucs\",\n+ \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+ \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/kszucs\"\n+ }\n+ },\n+ \"organization\": {\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+ \"description\": \"Innovation lab for open source data science tools, powered by Apache Arrow\",\n+ \"events_url\": \"https://api.github.com/orgs/ursa-labs/events\",\n+ \"hooks_url\": \"https://api.github.com/orgs/ursa-labs/hooks\",\n+ \"id\": 46514972,\n+ \"issues_url\": \"https://api.github.com/orgs/ursa-labs/issues\",\n+ \"login\": \"ursa-labs\",\n+ \"members_url\": \"https://api.github.com/orgs/ursa-labs/members{/member}\",\n+ \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+ \"public_members_url\": \"https://api.github.com/orgs/ursa-labs/public_members{/member}\",\n+ \"repos_url\": \"https://api.github.com/orgs/ursa-labs/repos\",\n+ \"url\": \"https://api.github.com/orgs/ursa-labs\"\n+ },\n+ \"repository\": {\n+ \"archive_url\": \"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\",\n+ \"archived\": false,\n+ \"assignees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\",\n+ \"blobs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\",\n+ \"branches_url\": \"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\",\n+ \"clone_url\": \"https://github.com/ursa-labs/ursabot.git\",\n+ \"collaborators_url\": \"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\",\n+ \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\",\n+ \"commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\",\n+ \"compare_url\": \"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\",\n+ \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\",\n+ \"contributors_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contributors\",\n+ \"created_at\": \"2019-02-04T15:40:31Z\",\n+ \"default_branch\": \"master\",\n+ \"deployments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/deployments\",\n+ \"description\": null,\n+ \"disabled\": false,\n+ \"downloads_url\": \"https://api.github.com/repos/ursa-labs/ursabot/downloads\",\n+ \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/events\",\n+ \"fork\": false,\n+ \"forks\": 0,\n+ \"forks_count\": 0,\n+ \"forks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/forks\",\n+ \"full_name\": \"ursa-labs/ursabot\",\n+ \"git_commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\",\n+ \"git_refs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\",\n+ \"git_tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\",\n+ \"git_url\": \"git://github.com/ursa-labs/ursabot.git\",\n+ \"has_downloads\": true,\n+ \"has_issues\": true,\n+ \"has_pages\": false,\n+ \"has_projects\": true,\n+ \"has_wiki\": true,\n+ \"homepage\": null,\n+ \"hooks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/hooks\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot\",\n+ \"id\": 169101701,\n+ \"issue_comment_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\",\n+ \"issue_events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\",\n+ \"issues_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\",\n+ \"keys_url\": \"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\",\n+ \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\",\n+ \"language\": \"Jupyter Notebook\",\n+ \"languages_url\": \"https://api.github.com/repos/ursa-labs/ursabot/languages\",\n+ \"license\": null,\n+ \"merges_url\": \"https://api.github.com/repos/ursa-labs/ursabot/merges\",\n+ \"milestones_url\": \"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\",\n+ \"mirror_url\": null,\n+ \"name\": \"ursabot\",\n+ \"node_id\": \"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\",\n+ \"notifications_url\": \"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\",\n+ \"open_issues\": 19,\n+ \"open_issues_count\": 19,\n+ \"owner\": {\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+ \"events_url\": \"https://api.github.com/users/ursa-labs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/ursa-labs/followers\",\n+ \"following_url\": \"https://api.github.com/users/ursa-labs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/ursa-labs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/ursa-labs\",\n+ \"id\": 46514972,\n+ \"login\": \"ursa-labs\",\n+ \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+ \"organizations_url\": \"https://api.github.com/users/ursa-labs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/ursa-labs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/ursa-labs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/ursa-labs/subscriptions\",\n+ \"type\": \"Organization\",\n+ \"url\": \"https://api.github.com/users/ursa-labs\"\n+ },\n+ \"private\": false,\n+ \"pulls_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\",\n+ \"pushed_at\": \"2019-04-05T11:22:16Z\",\n+ \"releases_url\": \"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\",\n+ \"size\": 892,\n+ \"ssh_url\": \"git@github.com:ursa-labs/ursabot.git\",\n+ \"stargazers_count\": 1,\n+ \"stargazers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/stargazers\",\n+ \"statuses_url\": \"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\",\n+ \"subscribers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscribers\",\n+ \"subscription_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscription\",\n+ \"svn_url\": \"https://github.com/ursa-labs/ursabot\",\n+ \"tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/tags\",\n+ \"teams_url\": \"https://api.github.com/repos/ursa-labs/ursabot/teams\",\n+ \"trees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\",\n+ \"updated_at\": \"2019-04-04T17:49:10Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+ \"watchers\": 1,\n+ \"watchers_count\": 1\n+ },\n+ \"sender\": {\n+ \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+ \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+ \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/kszucs\",\n+ \"id\": 961747,\n+ \"login\": \"kszucs\",\n+ \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+ \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/kszucs\"\n+ }\n+}" - }, - { - "sha": "2770e29ba9086394455315e590c0b433d08e437e", - "filename": "ursabot/tests/fixtures/issue-comment-with-empty-command.json", - "status": "added", - "additions": 212, - "deletions": 0, - "changes": 212, - "blob_url": "https://github.com/ursa-labs/ursabot/blob/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/fixtures/issue-comment-with-empty-command.json", - "raw_url": "https://github.com/ursa-labs/ursabot/raw/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/fixtures/issue-comment-with-empty-command.json", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-with-empty-command.json?ref=70267dee34884e4b972388e1b30d57f6248c58d0", - "patch": "@@ -0,0 +1,212 @@\n+{\n+ \"action\": \"created\",\n+ \"comment\": {\n+ \"author_association\": \"MEMBER\",\n+ \"body\": \"@ursabot \",\n+ \"created_at\": \"2019-04-05T11:35:46Z\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480243811\",\n+ \"id\": 480243811,\n+ \"issue_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+ \"node_id\": \"MDEyOklzc3VlQ29tbWVudDQ4MDI0MzgxMQ==\",\n+ \"updated_at\": \"2019-04-05T11:35:46Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480243811\",\n+ \"user\": {\n+ \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+ \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+ \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/kszucs\",\n+ \"id\": 961747,\n+ \"login\": \"kszucs\",\n+ \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+ \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/kszucs\"\n+ }\n+ },\n+ \"issue\": {\n+ \"assignee\": null,\n+ \"assignees\": [],\n+ \"author_association\": \"MEMBER\",\n+ \"body\": \"\",\n+ \"closed_at\": null,\n+ \"comments\": 1,\n+ \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments\",\n+ \"created_at\": \"2019-04-05T11:22:15Z\",\n+ \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/events\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+ \"id\": 429706959,\n+ \"labels\": [],\n+ \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}\",\n+ \"locked\": false,\n+ \"milestone\": null,\n+ \"node_id\": \"MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy\",\n+ \"number\": 26,\n+ \"pull_request\": {\n+ \"diff_url\": \"https://github.com/ursa-labs/ursabot/pull/26.diff\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+ \"patch_url\": \"https://github.com/ursa-labs/ursabot/pull/26.patch\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/26\"\n+ },\n+ \"repository_url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+ \"state\": \"open\",\n+ \"title\": \"Unittests for GithubHook\",\n+ \"updated_at\": \"2019-04-05T11:35:46Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+ \"user\": {\n+ \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+ \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+ \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/kszucs\",\n+ \"id\": 961747,\n+ \"login\": \"kszucs\",\n+ \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+ \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/kszucs\"\n+ }\n+ },\n+ \"organization\": {\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+ \"description\": \"Innovation lab for open source data science tools, powered by Apache Arrow\",\n+ \"events_url\": \"https://api.github.com/orgs/ursa-labs/events\",\n+ \"hooks_url\": \"https://api.github.com/orgs/ursa-labs/hooks\",\n+ \"id\": 46514972,\n+ \"issues_url\": \"https://api.github.com/orgs/ursa-labs/issues\",\n+ \"login\": \"ursa-labs\",\n+ \"members_url\": \"https://api.github.com/orgs/ursa-labs/members{/member}\",\n+ \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+ \"public_members_url\": \"https://api.github.com/orgs/ursa-labs/public_members{/member}\",\n+ \"repos_url\": \"https://api.github.com/orgs/ursa-labs/repos\",\n+ \"url\": \"https://api.github.com/orgs/ursa-labs\"\n+ },\n+ \"repository\": {\n+ \"archive_url\": \"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\",\n+ \"archived\": false,\n+ \"assignees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\",\n+ \"blobs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\",\n+ \"branches_url\": \"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\",\n+ \"clone_url\": \"https://github.com/ursa-labs/ursabot.git\",\n+ \"collaborators_url\": \"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\",\n+ \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\",\n+ \"commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\",\n+ \"compare_url\": \"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\",\n+ \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\",\n+ \"contributors_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contributors\",\n+ \"created_at\": \"2019-02-04T15:40:31Z\",\n+ \"default_branch\": \"master\",\n+ \"deployments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/deployments\",\n+ \"description\": null,\n+ \"disabled\": false,\n+ \"downloads_url\": \"https://api.github.com/repos/ursa-labs/ursabot/downloads\",\n+ \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/events\",\n+ \"fork\": false,\n+ \"forks\": 0,\n+ \"forks_count\": 0,\n+ \"forks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/forks\",\n+ \"full_name\": \"ursa-labs/ursabot\",\n+ \"git_commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\",\n+ \"git_refs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\",\n+ \"git_tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\",\n+ \"git_url\": \"git://github.com/ursa-labs/ursabot.git\",\n+ \"has_downloads\": true,\n+ \"has_issues\": true,\n+ \"has_pages\": false,\n+ \"has_projects\": true,\n+ \"has_wiki\": true,\n+ \"homepage\": null,\n+ \"hooks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/hooks\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot\",\n+ \"id\": 169101701,\n+ \"issue_comment_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\",\n+ \"issue_events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\",\n+ \"issues_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\",\n+ \"keys_url\": \"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\",\n+ \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\",\n+ \"language\": \"Jupyter Notebook\",\n+ \"languages_url\": \"https://api.github.com/repos/ursa-labs/ursabot/languages\",\n+ \"license\": null,\n+ \"merges_url\": \"https://api.github.com/repos/ursa-labs/ursabot/merges\",\n+ \"milestones_url\": \"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\",\n+ \"mirror_url\": null,\n+ \"name\": \"ursabot\",\n+ \"node_id\": \"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\",\n+ \"notifications_url\": \"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\",\n+ \"open_issues\": 19,\n+ \"open_issues_count\": 19,\n+ \"owner\": {\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+ \"events_url\": \"https://api.github.com/users/ursa-labs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/ursa-labs/followers\",\n+ \"following_url\": \"https://api.github.com/users/ursa-labs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/ursa-labs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/ursa-labs\",\n+ \"id\": 46514972,\n+ \"login\": \"ursa-labs\",\n+ \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+ \"organizations_url\": \"https://api.github.com/users/ursa-labs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/ursa-labs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/ursa-labs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/ursa-labs/subscriptions\",\n+ \"type\": \"Organization\",\n+ \"url\": \"https://api.github.com/users/ursa-labs\"\n+ },\n+ \"private\": false,\n+ \"pulls_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\",\n+ \"pushed_at\": \"2019-04-05T11:22:16Z\",\n+ \"releases_url\": \"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\",\n+ \"size\": 892,\n+ \"ssh_url\": \"git@github.com:ursa-labs/ursabot.git\",\n+ \"stargazers_count\": 1,\n+ \"stargazers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/stargazers\",\n+ \"statuses_url\": \"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\",\n+ \"subscribers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscribers\",\n+ \"subscription_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscription\",\n+ \"svn_url\": \"https://github.com/ursa-labs/ursabot\",\n+ \"tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/tags\",\n+ \"teams_url\": \"https://api.github.com/repos/ursa-labs/ursabot/teams\",\n+ \"trees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\",\n+ \"updated_at\": \"2019-04-04T17:49:10Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+ \"watchers\": 1,\n+ \"watchers_count\": 1\n+ },\n+ \"sender\": {\n+ \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+ \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+ \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/kszucs\",\n+ \"id\": 961747,\n+ \"login\": \"kszucs\",\n+ \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+ \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/kszucs\"\n+ }\n+}" - }, - { - "sha": "b7de8d838332944101812ee2a46c08dd0144efe3", - "filename": "ursabot/tests/fixtures/issue-comment-without-pull-request.json", - "status": "added", - "additions": 206, - "deletions": 0, - "changes": 206, - "blob_url": "https://github.com/ursa-labs/ursabot/blob/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/fixtures/issue-comment-without-pull-request.json", - "raw_url": "https://github.com/ursa-labs/ursabot/raw/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/fixtures/issue-comment-without-pull-request.json", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-without-pull-request.json?ref=70267dee34884e4b972388e1b30d57f6248c58d0", - "patch": "@@ -0,0 +1,206 @@\n+{\n+ \"action\": \"created\",\n+ \"comment\": {\n+ \"author_association\": \"MEMBER\",\n+ \"body\": \"@ursabot build\",\n+ \"created_at\": \"2019-04-05T13:07:57Z\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/issues/19#issuecomment-480268708\",\n+ \"id\": 480268708,\n+ \"issue_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/19\",\n+ \"node_id\": \"MDEyOklzc3VlQ29tbWVudDQ4MDI2ODcwOA==\",\n+ \"updated_at\": \"2019-04-05T13:07:57Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480268708\",\n+ \"user\": {\n+ \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+ \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+ \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/kszucs\",\n+ \"id\": 961747,\n+ \"login\": \"kszucs\",\n+ \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+ \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/kszucs\"\n+ }\n+ },\n+ \"issue\": {\n+ \"assignee\": null,\n+ \"assignees\": [],\n+ \"author_association\": \"MEMBER\",\n+ \"body\": \"\",\n+ \"closed_at\": null,\n+ \"comments\": 5,\n+ \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/19/comments\",\n+ \"created_at\": \"2019-04-02T09:56:41Z\",\n+ \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/19/events\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/issues/19\",\n+ \"id\": 428131685,\n+ \"labels\": [],\n+ \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/19/labels{/name}\",\n+ \"locked\": false,\n+ \"milestone\": null,\n+ \"node_id\": \"MDU6SXNzdWU0MjgxMzE2ODU=\",\n+ \"number\": 19,\n+ \"repository_url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+ \"state\": \"open\",\n+ \"title\": \"Build ursabot itself via ursabot\",\n+ \"updated_at\": \"2019-04-05T13:07:57Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/19\",\n+ \"user\": {\n+ \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+ \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+ \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/kszucs\",\n+ \"id\": 961747,\n+ \"login\": \"kszucs\",\n+ \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+ \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/kszucs\"\n+ }\n+ },\n+ \"organization\": {\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+ \"description\": \"Innovation lab for open source data science tools, powered by Apache Arrow\",\n+ \"events_url\": \"https://api.github.com/orgs/ursa-labs/events\",\n+ \"hooks_url\": \"https://api.github.com/orgs/ursa-labs/hooks\",\n+ \"id\": 46514972,\n+ \"issues_url\": \"https://api.github.com/orgs/ursa-labs/issues\",\n+ \"login\": \"ursa-labs\",\n+ \"members_url\": \"https://api.github.com/orgs/ursa-labs/members{/member}\",\n+ \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+ \"public_members_url\": \"https://api.github.com/orgs/ursa-labs/public_members{/member}\",\n+ \"repos_url\": \"https://api.github.com/orgs/ursa-labs/repos\",\n+ \"url\": \"https://api.github.com/orgs/ursa-labs\"\n+ },\n+ \"repository\": {\n+ \"archive_url\": \"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\",\n+ \"archived\": false,\n+ \"assignees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\",\n+ \"blobs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\",\n+ \"branches_url\": \"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\",\n+ \"clone_url\": \"https://github.com/ursa-labs/ursabot.git\",\n+ \"collaborators_url\": \"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\",\n+ \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\",\n+ \"commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\",\n+ \"compare_url\": \"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\",\n+ \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\",\n+ \"contributors_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contributors\",\n+ \"created_at\": \"2019-02-04T15:40:31Z\",\n+ \"default_branch\": \"master\",\n+ \"deployments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/deployments\",\n+ \"description\": null,\n+ \"disabled\": false,\n+ \"downloads_url\": \"https://api.github.com/repos/ursa-labs/ursabot/downloads\",\n+ \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/events\",\n+ \"fork\": false,\n+ \"forks\": 0,\n+ \"forks_count\": 0,\n+ \"forks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/forks\",\n+ \"full_name\": \"ursa-labs/ursabot\",\n+ \"git_commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\",\n+ \"git_refs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\",\n+ \"git_tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\",\n+ \"git_url\": \"git://github.com/ursa-labs/ursabot.git\",\n+ \"has_downloads\": true,\n+ \"has_issues\": true,\n+ \"has_pages\": false,\n+ \"has_projects\": true,\n+ \"has_wiki\": true,\n+ \"homepage\": null,\n+ \"hooks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/hooks\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot\",\n+ \"id\": 169101701,\n+ \"issue_comment_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\",\n+ \"issue_events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\",\n+ \"issues_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\",\n+ \"keys_url\": \"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\",\n+ \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\",\n+ \"language\": \"Jupyter Notebook\",\n+ \"languages_url\": \"https://api.github.com/repos/ursa-labs/ursabot/languages\",\n+ \"license\": null,\n+ \"merges_url\": \"https://api.github.com/repos/ursa-labs/ursabot/merges\",\n+ \"milestones_url\": \"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\",\n+ \"mirror_url\": null,\n+ \"name\": \"ursabot\",\n+ \"node_id\": \"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\",\n+ \"notifications_url\": \"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\",\n+ \"open_issues\": 19,\n+ \"open_issues_count\": 19,\n+ \"owner\": {\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+ \"events_url\": \"https://api.github.com/users/ursa-labs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/ursa-labs/followers\",\n+ \"following_url\": \"https://api.github.com/users/ursa-labs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/ursa-labs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/ursa-labs\",\n+ \"id\": 46514972,\n+ \"login\": \"ursa-labs\",\n+ \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+ \"organizations_url\": \"https://api.github.com/users/ursa-labs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/ursa-labs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/ursa-labs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/ursa-labs/subscriptions\",\n+ \"type\": \"Organization\",\n+ \"url\": \"https://api.github.com/users/ursa-labs\"\n+ },\n+ \"private\": false,\n+ \"pulls_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\",\n+ \"pushed_at\": \"2019-04-05T12:01:40Z\",\n+ \"releases_url\": \"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\",\n+ \"size\": 898,\n+ \"ssh_url\": \"git@github.com:ursa-labs/ursabot.git\",\n+ \"stargazers_count\": 1,\n+ \"stargazers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/stargazers\",\n+ \"statuses_url\": \"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\",\n+ \"subscribers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscribers\",\n+ \"subscription_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscription\",\n+ \"svn_url\": \"https://github.com/ursa-labs/ursabot\",\n+ \"tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/tags\",\n+ \"teams_url\": \"https://api.github.com/repos/ursa-labs/ursabot/teams\",\n+ \"trees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\",\n+ \"updated_at\": \"2019-04-04T17:49:10Z\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+ \"watchers\": 1,\n+ \"watchers_count\": 1\n+ },\n+ \"sender\": {\n+ \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+ \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+ \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+ \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+ \"gravatar_id\": \"\",\n+ \"html_url\": \"https://github.com/kszucs\",\n+ \"id\": 961747,\n+ \"login\": \"kszucs\",\n+ \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+ \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+ \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+ \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+ \"site_admin\": false,\n+ \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+ \"type\": \"User\",\n+ \"url\": \"https://api.github.com/users/kszucs\"\n+ }\n+}" - }, - { - "sha": "33e051455e866fb4774a16ae02ad40dcf9e6a7fd", - "filename": "ursabot/tests/fixtures/pull-request-26-commit.json", - "status": "added", - "additions": 158, - "deletions": 0, - "changes": 158, - "blob_url": "https://github.com/ursa-labs/ursabot/blob/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/fixtures/pull-request-26-commit.json", - "raw_url": "https://github.com/ursa-labs/ursabot/raw/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/fixtures/pull-request-26-commit.json", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/pull-request-26-commit.json?ref=70267dee34884e4b972388e1b30d57f6248c58d0", - "patch": "@@ -0,0 +1,158 @@\n+{\n+ \"sha\": \"2705da2b616b98fa6010a25813c5a7a27456f71d\",\n+ \"node_id\": \"MDY6Q29tbWl0MTY5MTAxNzAxOjI3MDVkYTJiNjE2Yjk4ZmE2MDEwYTI1ODEzYzVhN2EyNzQ1NmY3MWQ=\",\n+ \"commit\": {\n+ \"author\": {\n+ \"name\": \"Krisztián Szűcs\",\n+ \"email\": \"szucs.krisztian@gmail.com\",\n+ \"date\": \"2019-04-05T12:01:31Z\"\n+ },\n+ \"committer\": {\n+ \"name\": \"Krisztián Szűcs\",\n+ \"email\": \"szucs.krisztian@gmail.com\",\n+ \"date\": \"2019-04-05T12:01:31Z\"\n+ },\n+ \"message\": \"add recorded event requests\",\n+ \"tree\": {\n+ \"sha\": \"16a7bb186833a67e9c2d84a58393503b85500ceb\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/trees/16a7bb186833a67e9c2d84a58393503b85500ceb\"\n+ },\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/commits/2705da2b616b98fa6010a25813c5a7a27456f71d\",\n+ \"comment_count\": 0,\n+ \"verification\": {\n+ \"verified\": true,\n+ \"reason\": \"valid\",\n+ \"signature\": \"-----BEGIN PGP SIGNATURE-----\\n\\niQFOBAABCAA4FiEEOOW2r8dr6sA77zHlgjqBKYe1QKUFAlynQ58aHHN6dWNzLmty\\naXN6dGlhbkBnbWFpbC5jb20ACgkQgjqBKYe1QKUYKwf6AiXDMaLqNLNSjRY7lIXX\\nudioewz0hSb4bgIXBv30nswu9CoOA0+mHCokEVtZhYbXzXDsZ1KJrilSC4j+Ws4q\\nkRGA6iEmrne2HcSKNZXzcVnwV9zpwKxlVh2QCTNb1PuOYFBLH0kwE704uWIWMGDN\\nbo8cjQPwegePCRguCvPh/5wa5J3uiq5gmJLG6bC/d1XYE+FJVtlnyzqzLMIryGKe\\ntIciw+wwkF413Q/YVbZ49vLUeCX9H8PHC4mZYGDWuvjFW1WTfkjK5bAH+oaTVM6h\\n350I5ZFloHmMA/QeRge5qFxXoEBMDGiXHHktzYZDXnliFOQNxzqwirA5lQQ6LRSS\\naQ==\\n=7rqi\\n-----END PGP SIGNATURE-----\",\n+ \"payload\": \"tree 16a7bb186833a67e9c2d84a58393503b85500ceb\\nparent 446ae69b9385e8d0f40aa9595f723d34383af2f7\\nauthor Krisztián Szűcs 1554465691 +0200\\ncommitter Krisztián Szűcs 1554465691 +0200\\n\\nadd recorded event requests\\n\"\n+ }\n+ },\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits/2705da2b616b98fa6010a25813c5a7a27456f71d\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/commit/2705da2b616b98fa6010a25813c5a7a27456f71d\",\n+ \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits/2705da2b616b98fa6010a25813c5a7a27456f71d/comments\",\n+ \"author\": {\n+ \"login\": \"kszucs\",\n+ \"id\": 961747,\n+ \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+ \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+ \"gravatar_id\": \"\",\n+ \"url\": \"https://api.github.com/users/kszucs\",\n+ \"html_url\": \"https://github.com/kszucs\",\n+ \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+ \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+ \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+ \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+ \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+ \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+ \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+ \"type\": \"User\",\n+ \"site_admin\": false\n+ },\n+ \"committer\": {\n+ \"login\": \"kszucs\",\n+ \"id\": 961747,\n+ \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+ \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+ \"gravatar_id\": \"\",\n+ \"url\": \"https://api.github.com/users/kszucs\",\n+ \"html_url\": \"https://github.com/kszucs\",\n+ \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+ \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+ \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+ \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+ \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+ \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+ \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+ \"type\": \"User\",\n+ \"site_admin\": false\n+ },\n+ \"parents\": [\n+ {\n+ \"sha\": \"446ae69b9385e8d0f40aa9595f723d34383af2f7\",\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits/446ae69b9385e8d0f40aa9595f723d34383af2f7\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/commit/446ae69b9385e8d0f40aa9595f723d34383af2f7\"\n+ }\n+ ],\n+ \"stats\": {\n+ \"total\": 1062,\n+ \"additions\": 1058,\n+ \"deletions\": 4\n+ },\n+ \"files\": [\n+ {\n+ \"sha\": \"dfae6eeaef384ae6180c6302a58b49e39982dc33\",\n+ \"filename\": \"ursabot/tests/fixtures/issue-comment-build-command.json\",\n+ \"status\": \"added\",\n+ \"additions\": 212,\n+ \"deletions\": 0,\n+ \"changes\": 212,\n+ \"blob_url\": \"https://github.com/ursa-labs/ursabot/blob/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-build-command.json\",\n+ \"raw_url\": \"https://github.com/ursa-labs/ursabot/raw/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-build-command.json\",\n+ \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-build-command.json?ref=2705da2b616b98fa6010a25813c5a7a27456f71d\",\n+ \"patch\": \"@@ -0,0 +1,212 @@\\n+{\\n+ \\\"action\\\": \\\"created\\\",\\n+ \\\"comment\\\": {\\n+ \\\"author_association\\\": \\\"NONE\\\",\\n+ \\\"body\\\": \\\"I've successfully started builds for this PR\\\",\\n+ \\\"created_at\\\": \\\"2019-04-05T11:55:44Z\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480248730\\\",\\n+ \\\"id\\\": 480248730,\\n+ \\\"issue_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26\\\",\\n+ \\\"node_id\\\": \\\"MDEyOklzc3VlQ29tbWVudDQ4MDI0ODczMA==\\\",\\n+ \\\"updated_at\\\": \\\"2019-04-05T11:55:44Z\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480248730\\\",\\n+ \\\"user\\\": {\\n+ \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/49275095?v=4\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/users/ursabot/events{/privacy}\\\",\\n+ \\\"followers_url\\\": \\\"https://api.github.com/users/ursabot/followers\\\",\\n+ \\\"following_url\\\": \\\"https://api.github.com/users/ursabot/following{/other_user}\\\",\\n+ \\\"gists_url\\\": \\\"https://api.github.com/users/ursabot/gists{/gist_id}\\\",\\n+ \\\"gravatar_id\\\": \\\"\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursabot\\\",\\n+ \\\"id\\\": 49275095,\\n+ \\\"login\\\": \\\"ursabot\\\",\\n+ \\\"node_id\\\": \\\"MDQ6VXNlcjQ5Mjc1MDk1\\\",\\n+ \\\"organizations_url\\\": \\\"https://api.github.com/users/ursabot/orgs\\\",\\n+ \\\"received_events_url\\\": \\\"https://api.github.com/users/ursabot/received_events\\\",\\n+ \\\"repos_url\\\": \\\"https://api.github.com/users/ursabot/repos\\\",\\n+ \\\"site_admin\\\": false,\\n+ \\\"starred_url\\\": \\\"https://api.github.com/users/ursabot/starred{/owner}{/repo}\\\",\\n+ \\\"subscriptions_url\\\": \\\"https://api.github.com/users/ursabot/subscriptions\\\",\\n+ \\\"type\\\": \\\"User\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/users/ursabot\\\"\\n+ }\\n+ },\\n+ \\\"issue\\\": {\\n+ \\\"assignee\\\": null,\\n+ \\\"assignees\\\": [],\\n+ \\\"author_association\\\": \\\"MEMBER\\\",\\n+ \\\"body\\\": \\\"\\\",\\n+ \\\"closed_at\\\": null,\\n+ \\\"comments\\\": 4,\\n+ \\\"comments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments\\\",\\n+ \\\"created_at\\\": \\\"2019-04-05T11:22:15Z\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26/events\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26\\\",\\n+ \\\"id\\\": 429706959,\\n+ \\\"labels\\\": [],\\n+ \\\"labels_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}\\\",\\n+ \\\"locked\\\": false,\\n+ \\\"milestone\\\": null,\\n+ \\\"node_id\\\": \\\"MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy\\\",\\n+ \\\"number\\\": 26,\\n+ \\\"pull_request\\\": {\\n+ \\\"diff_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26.diff\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26\\\",\\n+ \\\"patch_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26.patch\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/pulls/26\\\"\\n+ },\\n+ \\\"repository_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot\\\",\\n+ \\\"state\\\": \\\"open\\\",\\n+ \\\"title\\\": \\\"Unittests for GithubHook\\\",\\n+ \\\"updated_at\\\": \\\"2019-04-05T11:55:44Z\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26\\\",\\n+ \\\"user\\\": {\\n+ \\\"avatar_url\\\": \\\"https://avatars1.githubusercontent.com/u/961747?v=4\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/users/kszucs/events{/privacy}\\\",\\n+ \\\"followers_url\\\": \\\"https://api.github.com/users/kszucs/followers\\\",\\n+ \\\"following_url\\\": \\\"https://api.github.com/users/kszucs/following{/other_user}\\\",\\n+ \\\"gists_url\\\": \\\"https://api.github.com/users/kszucs/gists{/gist_id}\\\",\\n+ \\\"gravatar_id\\\": \\\"\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/kszucs\\\",\\n+ \\\"id\\\": 961747,\\n+ \\\"login\\\": \\\"kszucs\\\",\\n+ \\\"node_id\\\": \\\"MDQ6VXNlcjk2MTc0Nw==\\\",\\n+ \\\"organizations_url\\\": \\\"https://api.github.com/users/kszucs/orgs\\\",\\n+ \\\"received_events_url\\\": \\\"https://api.github.com/users/kszucs/received_events\\\",\\n+ \\\"repos_url\\\": \\\"https://api.github.com/users/kszucs/repos\\\",\\n+ \\\"site_admin\\\": false,\\n+ \\\"starred_url\\\": \\\"https://api.github.com/users/kszucs/starred{/owner}{/repo}\\\",\\n+ \\\"subscriptions_url\\\": \\\"https://api.github.com/users/kszucs/subscriptions\\\",\\n+ \\\"type\\\": \\\"User\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/users/kszucs\\\"\\n+ }\\n+ },\\n+ \\\"organization\\\": {\\n+ \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/46514972?v=4\\\",\\n+ \\\"description\\\": \\\"Innovation lab for open source data science tools, powered by Apache Arrow\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/orgs/ursa-labs/events\\\",\\n+ \\\"hooks_url\\\": \\\"https://api.github.com/orgs/ursa-labs/hooks\\\",\\n+ \\\"id\\\": 46514972,\\n+ \\\"issues_url\\\": \\\"https://api.github.com/orgs/ursa-labs/issues\\\",\\n+ \\\"login\\\": \\\"ursa-labs\\\",\\n+ \\\"members_url\\\": \\\"https://api.github.com/orgs/ursa-labs/members{/member}\\\",\\n+ \\\"node_id\\\": \\\"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\\\",\\n+ \\\"public_members_url\\\": \\\"https://api.github.com/orgs/ursa-labs/public_members{/member}\\\",\\n+ \\\"repos_url\\\": \\\"https://api.github.com/orgs/ursa-labs/repos\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/orgs/ursa-labs\\\"\\n+ },\\n+ \\\"repository\\\": {\\n+ \\\"archive_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\\\",\\n+ \\\"archived\\\": false,\\n+ \\\"assignees_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\\\",\\n+ \\\"blobs_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\\\",\\n+ \\\"branches_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\\\",\\n+ \\\"clone_url\\\": \\\"https://github.com/ursa-labs/ursabot.git\\\",\\n+ \\\"collaborators_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\\\",\\n+ \\\"comments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\\\",\\n+ \\\"commits_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\\\",\\n+ \\\"compare_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\\\",\\n+ \\\"contents_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\\\",\\n+ \\\"contributors_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/contributors\\\",\\n+ \\\"created_at\\\": \\\"2019-02-04T15:40:31Z\\\",\\n+ \\\"default_branch\\\": \\\"master\\\",\\n+ \\\"deployments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/deployments\\\",\\n+ \\\"description\\\": null,\\n+ \\\"disabled\\\": false,\\n+ \\\"downloads_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/downloads\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/events\\\",\\n+ \\\"fork\\\": false,\\n+ \\\"forks\\\": 0,\\n+ \\\"forks_count\\\": 0,\\n+ \\\"forks_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/forks\\\",\\n+ \\\"full_name\\\": \\\"ursa-labs/ursabot\\\",\\n+ \\\"git_commits_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\\\",\\n+ \\\"git_refs_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\\\",\\n+ \\\"git_tags_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\\\",\\n+ \\\"git_url\\\": \\\"git://github.com/ursa-labs/ursabot.git\\\",\\n+ \\\"has_downloads\\\": true,\\n+ \\\"has_issues\\\": true,\\n+ \\\"has_pages\\\": false,\\n+ \\\"has_projects\\\": true,\\n+ \\\"has_wiki\\\": true,\\n+ \\\"homepage\\\": null,\\n+ \\\"hooks_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/hooks\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot\\\",\\n+ \\\"id\\\": 169101701,\\n+ \\\"issue_comment_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\\\",\\n+ \\\"issue_events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\\\",\\n+ \\\"issues_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\\\",\\n+ \\\"keys_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\\\",\\n+ \\\"labels_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\\\",\\n+ \\\"language\\\": \\\"Jupyter Notebook\\\",\\n+ \\\"languages_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/languages\\\",\\n+ \\\"license\\\": null,\\n+ \\\"merges_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/merges\\\",\\n+ \\\"milestones_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\\\",\\n+ \\\"mirror_url\\\": null,\\n+ \\\"name\\\": \\\"ursabot\\\",\\n+ \\\"node_id\\\": \\\"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\\\",\\n+ \\\"notifications_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\\\",\\n+ \\\"open_issues\\\": 19,\\n+ \\\"open_issues_count\\\": 19,\\n+ \\\"owner\\\": {\\n+ \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/46514972?v=4\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/users/ursa-labs/events{/privacy}\\\",\\n+ \\\"followers_url\\\": \\\"https://api.github.com/users/ursa-labs/followers\\\",\\n+ \\\"following_url\\\": \\\"https://api.github.com/users/ursa-labs/following{/other_user}\\\",\\n+ \\\"gists_url\\\": \\\"https://api.github.com/users/ursa-labs/gists{/gist_id}\\\",\\n+ \\\"gravatar_id\\\": \\\"\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursa-labs\\\",\\n+ \\\"id\\\": 46514972,\\n+ \\\"login\\\": \\\"ursa-labs\\\",\\n+ \\\"node_id\\\": \\\"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\\\",\\n+ \\\"organizations_url\\\": \\\"https://api.github.com/users/ursa-labs/orgs\\\",\\n+ \\\"received_events_url\\\": \\\"https://api.github.com/users/ursa-labs/received_events\\\",\\n+ \\\"repos_url\\\": \\\"https://api.github.com/users/ursa-labs/repos\\\",\\n+ \\\"site_admin\\\": false,\\n+ \\\"starred_url\\\": \\\"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\\\",\\n+ \\\"subscriptions_url\\\": \\\"https://api.github.com/users/ursa-labs/subscriptions\\\",\\n+ \\\"type\\\": \\\"Organization\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/users/ursa-labs\\\"\\n+ },\\n+ \\\"private\\\": false,\\n+ \\\"pulls_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\\\",\\n+ \\\"pushed_at\\\": \\\"2019-04-05T11:22:16Z\\\",\\n+ \\\"releases_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\\\",\\n+ \\\"size\\\": 892,\\n+ \\\"ssh_url\\\": \\\"git@github.com:ursa-labs/ursabot.git\\\",\\n+ \\\"stargazers_count\\\": 1,\\n+ \\\"stargazers_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/stargazers\\\",\\n+ \\\"statuses_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\\\",\\n+ \\\"subscribers_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/subscribers\\\",\\n+ \\\"subscription_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/subscription\\\",\\n+ \\\"svn_url\\\": \\\"https://github.com/ursa-labs/ursabot\\\",\\n+ \\\"tags_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/tags\\\",\\n+ \\\"teams_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/teams\\\",\\n+ \\\"trees_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\\\",\\n+ \\\"updated_at\\\": \\\"2019-04-04T17:49:10Z\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot\\\",\\n+ \\\"watchers\\\": 1,\\n+ \\\"watchers_count\\\": 1\\n+ },\\n+ \\\"sender\\\": {\\n+ \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/49275095?v=4\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/users/ursabot/events{/privacy}\\\",\\n+ \\\"followers_url\\\": \\\"https://api.github.com/users/ursabot/followers\\\",\\n+ \\\"following_url\\\": \\\"https://api.github.com/users/ursabot/following{/other_user}\\\",\\n+ \\\"gists_url\\\": \\\"https://api.github.com/users/ursabot/gists{/gist_id}\\\",\\n+ \\\"gravatar_id\\\": \\\"\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursabot\\\",\\n+ \\\"id\\\": 49275095,\\n+ \\\"login\\\": \\\"ursabot\\\",\\n+ \\\"node_id\\\": \\\"MDQ6VXNlcjQ5Mjc1MDk1\\\",\\n+ \\\"organizations_url\\\": \\\"https://api.github.com/users/ursabot/orgs\\\",\\n+ \\\"received_events_url\\\": \\\"https://api.github.com/users/ursabot/received_events\\\",\\n+ \\\"repos_url\\\": \\\"https://api.github.com/users/ursabot/repos\\\",\\n+ \\\"site_admin\\\": false,\\n+ \\\"starred_url\\\": \\\"https://api.github.com/users/ursabot/starred{/owner}{/repo}\\\",\\n+ \\\"subscriptions_url\\\": \\\"https://api.github.com/users/ursabot/subscriptions\\\",\\n+ \\\"type\\\": \\\"User\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/users/ursabot\\\"\\n+ }\\n+}\"\n+ },\n+ {\n+ \"sha\": \"7ef554e333327f0e62aa1fd76b4b17844a39adeb\",\n+ \"filename\": \"ursabot/tests/fixtures/issue-comment-by-ursabot.json\",\n+ \"status\": \"added\",\n+ \"additions\": 212,\n+ \"deletions\": 0,\n+ \"changes\": 212,\n+ \"blob_url\": \"https://github.com/ursa-labs/ursabot/blob/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-by-ursabot.json\",\n+ \"raw_url\": \"https://github.com/ursa-labs/ursabot/raw/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-by-ursabot.json\",\n+ \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-by-ursabot.json?ref=2705da2b616b98fa6010a25813c5a7a27456f71d\",\n+ \"patch\": \"@@ -0,0 +1,212 @@\\n+{\\n+ \\\"action\\\": \\\"created\\\",\\n+ \\\"comment\\\": {\\n+ \\\"author_association\\\": \\\"NONE\\\",\\n+ \\\"body\\\": \\\"Unknown command \\\\\\\"\\\\\\\"\\\",\\n+ \\\"created_at\\\": \\\"2019-04-05T11:35:47Z\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480243815\\\",\\n+ \\\"id\\\": 480243815,\\n+ \\\"issue_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26\\\",\\n+ \\\"node_id\\\": \\\"MDEyOklzc3VlQ29tbWVudDQ4MDI0MzgxNQ==\\\",\\n+ \\\"updated_at\\\": \\\"2019-04-05T11:35:47Z\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480243815\\\",\\n+ \\\"user\\\": {\\n+ \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/49275095?v=4\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/users/ursabot/events{/privacy}\\\",\\n+ \\\"followers_url\\\": \\\"https://api.github.com/users/ursabot/followers\\\",\\n+ \\\"following_url\\\": \\\"https://api.github.com/users/ursabot/following{/other_user}\\\",\\n+ \\\"gists_url\\\": \\\"https://api.github.com/users/ursabot/gists{/gist_id}\\\",\\n+ \\\"gravatar_id\\\": \\\"\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursabot\\\",\\n+ \\\"id\\\": 49275095,\\n+ \\\"login\\\": \\\"ursabot\\\",\\n+ \\\"node_id\\\": \\\"MDQ6VXNlcjQ5Mjc1MDk1\\\",\\n+ \\\"organizations_url\\\": \\\"https://api.github.com/users/ursabot/orgs\\\",\\n+ \\\"received_events_url\\\": \\\"https://api.github.com/users/ursabot/received_events\\\",\\n+ \\\"repos_url\\\": \\\"https://api.github.com/users/ursabot/repos\\\",\\n+ \\\"site_admin\\\": false,\\n+ \\\"starred_url\\\": \\\"https://api.github.com/users/ursabot/starred{/owner}{/repo}\\\",\\n+ \\\"subscriptions_url\\\": \\\"https://api.github.com/users/ursabot/subscriptions\\\",\\n+ \\\"type\\\": \\\"User\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/users/ursabot\\\"\\n+ }\\n+ },\\n+ \\\"issue\\\": {\\n+ \\\"assignee\\\": null,\\n+ \\\"assignees\\\": [],\\n+ \\\"author_association\\\": \\\"MEMBER\\\",\\n+ \\\"body\\\": \\\"\\\",\\n+ \\\"closed_at\\\": null,\\n+ \\\"comments\\\": 2,\\n+ \\\"comments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments\\\",\\n+ \\\"created_at\\\": \\\"2019-04-05T11:22:15Z\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26/events\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26\\\",\\n+ \\\"id\\\": 429706959,\\n+ \\\"labels\\\": [],\\n+ \\\"labels_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}\\\",\\n+ \\\"locked\\\": false,\\n+ \\\"milestone\\\": null,\\n+ \\\"node_id\\\": \\\"MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy\\\",\\n+ \\\"number\\\": 26,\\n+ \\\"pull_request\\\": {\\n+ \\\"diff_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26.diff\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26\\\",\\n+ \\\"patch_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26.patch\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/pulls/26\\\"\\n+ },\\n+ \\\"repository_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot\\\",\\n+ \\\"state\\\": \\\"open\\\",\\n+ \\\"title\\\": \\\"Unittests for GithubHook\\\",\\n+ \\\"updated_at\\\": \\\"2019-04-05T11:35:47Z\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26\\\",\\n+ \\\"user\\\": {\\n+ \\\"avatar_url\\\": \\\"https://avatars1.githubusercontent.com/u/961747?v=4\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/users/kszucs/events{/privacy}\\\",\\n+ \\\"followers_url\\\": \\\"https://api.github.com/users/kszucs/followers\\\",\\n+ \\\"following_url\\\": \\\"https://api.github.com/users/kszucs/following{/other_user}\\\",\\n+ \\\"gists_url\\\": \\\"https://api.github.com/users/kszucs/gists{/gist_id}\\\",\\n+ \\\"gravatar_id\\\": \\\"\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/kszucs\\\",\\n+ \\\"id\\\": 961747,\\n+ \\\"login\\\": \\\"kszucs\\\",\\n+ \\\"node_id\\\": \\\"MDQ6VXNlcjk2MTc0Nw==\\\",\\n+ \\\"organizations_url\\\": \\\"https://api.github.com/users/kszucs/orgs\\\",\\n+ \\\"received_events_url\\\": \\\"https://api.github.com/users/kszucs/received_events\\\",\\n+ \\\"repos_url\\\": \\\"https://api.github.com/users/kszucs/repos\\\",\\n+ \\\"site_admin\\\": false,\\n+ \\\"starred_url\\\": \\\"https://api.github.com/users/kszucs/starred{/owner}{/repo}\\\",\\n+ \\\"subscriptions_url\\\": \\\"https://api.github.com/users/kszucs/subscriptions\\\",\\n+ \\\"type\\\": \\\"User\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/users/kszucs\\\"\\n+ }\\n+ },\\n+ \\\"organization\\\": {\\n+ \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/46514972?v=4\\\",\\n+ \\\"description\\\": \\\"Innovation lab for open source data science tools, powered by Apache Arrow\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/orgs/ursa-labs/events\\\",\\n+ \\\"hooks_url\\\": \\\"https://api.github.com/orgs/ursa-labs/hooks\\\",\\n+ \\\"id\\\": 46514972,\\n+ \\\"issues_url\\\": \\\"https://api.github.com/orgs/ursa-labs/issues\\\",\\n+ \\\"login\\\": \\\"ursa-labs\\\",\\n+ \\\"members_url\\\": \\\"https://api.github.com/orgs/ursa-labs/members{/member}\\\",\\n+ \\\"node_id\\\": \\\"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\\\",\\n+ \\\"public_members_url\\\": \\\"https://api.github.com/orgs/ursa-labs/public_members{/member}\\\",\\n+ \\\"repos_url\\\": \\\"https://api.github.com/orgs/ursa-labs/repos\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/orgs/ursa-labs\\\"\\n+ },\\n+ \\\"repository\\\": {\\n+ \\\"archive_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\\\",\\n+ \\\"archived\\\": false,\\n+ \\\"assignees_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\\\",\\n+ \\\"blobs_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\\\",\\n+ \\\"branches_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\\\",\\n+ \\\"clone_url\\\": \\\"https://github.com/ursa-labs/ursabot.git\\\",\\n+ \\\"collaborators_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\\\",\\n+ \\\"comments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\\\",\\n+ \\\"commits_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\\\",\\n+ \\\"compare_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\\\",\\n+ \\\"contents_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\\\",\\n+ \\\"contributors_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/contributors\\\",\\n+ \\\"created_at\\\": \\\"2019-02-04T15:40:31Z\\\",\\n+ \\\"default_branch\\\": \\\"master\\\",\\n+ \\\"deployments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/deployments\\\",\\n+ \\\"description\\\": null,\\n+ \\\"disabled\\\": false,\\n+ \\\"downloads_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/downloads\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/events\\\",\\n+ \\\"fork\\\": false,\\n+ \\\"forks\\\": 0,\\n+ \\\"forks_count\\\": 0,\\n+ \\\"forks_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/forks\\\",\\n+ \\\"full_name\\\": \\\"ursa-labs/ursabot\\\",\\n+ \\\"git_commits_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\\\",\\n+ \\\"git_refs_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\\\",\\n+ \\\"git_tags_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\\\",\\n+ \\\"git_url\\\": \\\"git://github.com/ursa-labs/ursabot.git\\\",\\n+ \\\"has_downloads\\\": true,\\n+ \\\"has_issues\\\": true,\\n+ \\\"has_pages\\\": false,\\n+ \\\"has_projects\\\": true,\\n+ \\\"has_wiki\\\": true,\\n+ \\\"homepage\\\": null,\\n+ \\\"hooks_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/hooks\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot\\\",\\n+ \\\"id\\\": 169101701,\\n+ \\\"issue_comment_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\\\",\\n+ \\\"issue_events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\\\",\\n+ \\\"issues_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\\\",\\n+ \\\"keys_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\\\",\\n+ \\\"labels_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\\\",\\n+ \\\"language\\\": \\\"Jupyter Notebook\\\",\\n+ \\\"languages_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/languages\\\",\\n+ \\\"license\\\": null,\\n+ \\\"merges_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/merges\\\",\\n+ \\\"milestones_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\\\",\\n+ \\\"mirror_url\\\": null,\\n+ \\\"name\\\": \\\"ursabot\\\",\\n+ \\\"node_id\\\": \\\"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\\\",\\n+ \\\"notifications_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\\\",\\n+ \\\"open_issues\\\": 19,\\n+ \\\"open_issues_count\\\": 19,\\n+ \\\"owner\\\": {\\n+ \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/46514972?v=4\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/users/ursa-labs/events{/privacy}\\\",\\n+ \\\"followers_url\\\": \\\"https://api.github.com/users/ursa-labs/followers\\\",\\n+ \\\"following_url\\\": \\\"https://api.github.com/users/ursa-labs/following{/other_user}\\\",\\n+ \\\"gists_url\\\": \\\"https://api.github.com/users/ursa-labs/gists{/gist_id}\\\",\\n+ \\\"gravatar_id\\\": \\\"\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursa-labs\\\",\\n+ \\\"id\\\": 46514972,\\n+ \\\"login\\\": \\\"ursa-labs\\\",\\n+ \\\"node_id\\\": \\\"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\\\",\\n+ \\\"organizations_url\\\": \\\"https://api.github.com/users/ursa-labs/orgs\\\",\\n+ \\\"received_events_url\\\": \\\"https://api.github.com/users/ursa-labs/received_events\\\",\\n+ \\\"repos_url\\\": \\\"https://api.github.com/users/ursa-labs/repos\\\",\\n+ \\\"site_admin\\\": false,\\n+ \\\"starred_url\\\": \\\"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\\\",\\n+ \\\"subscriptions_url\\\": \\\"https://api.github.com/users/ursa-labs/subscriptions\\\",\\n+ \\\"type\\\": \\\"Organization\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/users/ursa-labs\\\"\\n+ },\\n+ \\\"private\\\": false,\\n+ \\\"pulls_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\\\",\\n+ \\\"pushed_at\\\": \\\"2019-04-05T11:22:16Z\\\",\\n+ \\\"releases_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\\\",\\n+ \\\"size\\\": 892,\\n+ \\\"ssh_url\\\": \\\"git@github.com:ursa-labs/ursabot.git\\\",\\n+ \\\"stargazers_count\\\": 1,\\n+ \\\"stargazers_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/stargazers\\\",\\n+ \\\"statuses_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\\\",\\n+ \\\"subscribers_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/subscribers\\\",\\n+ \\\"subscription_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/subscription\\\",\\n+ \\\"svn_url\\\": \\\"https://github.com/ursa-labs/ursabot\\\",\\n+ \\\"tags_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/tags\\\",\\n+ \\\"teams_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/teams\\\",\\n+ \\\"trees_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\\\",\\n+ \\\"updated_at\\\": \\\"2019-04-04T17:49:10Z\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot\\\",\\n+ \\\"watchers\\\": 1,\\n+ \\\"watchers_count\\\": 1\\n+ },\\n+ \\\"sender\\\": {\\n+ \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/49275095?v=4\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/users/ursabot/events{/privacy}\\\",\\n+ \\\"followers_url\\\": \\\"https://api.github.com/users/ursabot/followers\\\",\\n+ \\\"following_url\\\": \\\"https://api.github.com/users/ursabot/following{/other_user}\\\",\\n+ \\\"gists_url\\\": \\\"https://api.github.com/users/ursabot/gists{/gist_id}\\\",\\n+ \\\"gravatar_id\\\": \\\"\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursabot\\\",\\n+ \\\"id\\\": 49275095,\\n+ \\\"login\\\": \\\"ursabot\\\",\\n+ \\\"node_id\\\": \\\"MDQ6VXNlcjQ5Mjc1MDk1\\\",\\n+ \\\"organizations_url\\\": \\\"https://api.github.com/users/ursabot/orgs\\\",\\n+ \\\"received_events_url\\\": \\\"https://api.github.com/users/ursabot/received_events\\\",\\n+ \\\"repos_url\\\": \\\"https://api.github.com/users/ursabot/repos\\\",\\n+ \\\"site_admin\\\": false,\\n+ \\\"starred_url\\\": \\\"https://api.github.com/users/ursabot/starred{/owner}{/repo}\\\",\\n+ \\\"subscriptions_url\\\": \\\"https://api.github.com/users/ursabot/subscriptions\\\",\\n+ \\\"type\\\": \\\"User\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/users/ursabot\\\"\\n+ }\\n+}\"\n+ },\n+ {\n+ \"sha\": \"a8082dbc91fdfe815b795e49ec10e49000771ef5\",\n+ \"filename\": \"ursabot/tests/fixtures/issue-comment-not-mentioning-ursabot.json\",\n+ \"status\": \"added\",\n+ \"additions\": 212,\n+ \"deletions\": 0,\n+ \"changes\": 212,\n+ \"blob_url\": \"https://github.com/ursa-labs/ursabot/blob/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-not-mentioning-ursabot.json\",\n+ \"raw_url\": \"https://github.com/ursa-labs/ursabot/raw/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-not-mentioning-ursabot.json\",\n+ \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-not-mentioning-ursabot.json?ref=2705da2b616b98fa6010a25813c5a7a27456f71d\",\n+ \"patch\": \"@@ -0,0 +1,212 @@\\n+{\\n+ \\\"action\\\": \\\"created\\\",\\n+ \\\"comment\\\": {\\n+ \\\"author_association\\\": \\\"MEMBER\\\",\\n+ \\\"body\\\": \\\"bear is no game\\\",\\n+ \\\"created_at\\\": \\\"2019-04-05T11:26:56Z\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480241727\\\",\\n+ \\\"id\\\": 480241727,\\n+ \\\"issue_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26\\\",\\n+ \\\"node_id\\\": \\\"MDEyOklzc3VlQ29tbWVudDQ4MDI0MTcyNw==\\\",\\n+ \\\"updated_at\\\": \\\"2019-04-05T11:26:56Z\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480241727\\\",\\n+ \\\"user\\\": {\\n+ \\\"avatar_url\\\": \\\"https://avatars1.githubusercontent.com/u/961747?v=4\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/users/kszucs/events{/privacy}\\\",\\n+ \\\"followers_url\\\": \\\"https://api.github.com/users/kszucs/followers\\\",\\n+ \\\"following_url\\\": \\\"https://api.github.com/users/kszucs/following{/other_user}\\\",\\n+ \\\"gists_url\\\": \\\"https://api.github.com/users/kszucs/gists{/gist_id}\\\",\\n+ \\\"gravatar_id\\\": \\\"\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/kszucs\\\",\\n+ \\\"id\\\": 961747,\\n+ \\\"login\\\": \\\"kszucs\\\",\\n+ \\\"node_id\\\": \\\"MDQ6VXNlcjk2MTc0Nw==\\\",\\n+ \\\"organizations_url\\\": \\\"https://api.github.com/users/kszucs/orgs\\\",\\n+ \\\"received_events_url\\\": \\\"https://api.github.com/users/kszucs/received_events\\\",\\n+ \\\"repos_url\\\": \\\"https://api.github.com/users/kszucs/repos\\\",\\n+ \\\"site_admin\\\": false,\\n+ \\\"starred_url\\\": \\\"https://api.github.com/users/kszucs/starred{/owner}{/repo}\\\",\\n+ \\\"subscriptions_url\\\": \\\"https://api.github.com/users/kszucs/subscriptions\\\",\\n+ \\\"type\\\": \\\"User\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/users/kszucs\\\"\\n+ }\\n+ },\\n+ \\\"issue\\\": {\\n+ \\\"assignee\\\": null,\\n+ \\\"assignees\\\": [],\\n+ \\\"author_association\\\": \\\"MEMBER\\\",\\n+ \\\"body\\\": \\\"\\\",\\n+ \\\"closed_at\\\": null,\\n+ \\\"comments\\\": 0,\\n+ \\\"comments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments\\\",\\n+ \\\"created_at\\\": \\\"2019-04-05T11:22:15Z\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26/events\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26\\\",\\n+ \\\"id\\\": 429706959,\\n+ \\\"labels\\\": [],\\n+ \\\"labels_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}\\\",\\n+ \\\"locked\\\": false,\\n+ \\\"milestone\\\": null,\\n+ \\\"node_id\\\": \\\"MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy\\\",\\n+ \\\"number\\\": 26,\\n+ \\\"pull_request\\\": {\\n+ \\\"diff_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26.diff\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26\\\",\\n+ \\\"patch_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26.patch\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/pulls/26\\\"\\n+ },\\n+ \\\"repository_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot\\\",\\n+ \\\"state\\\": \\\"open\\\",\\n+ \\\"title\\\": \\\"Unittests for GithubHook\\\",\\n+ \\\"updated_at\\\": \\\"2019-04-05T11:26:56Z\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26\\\",\\n+ \\\"user\\\": {\\n+ \\\"avatar_url\\\": \\\"https://avatars1.githubusercontent.com/u/961747?v=4\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/users/kszucs/events{/privacy}\\\",\\n+ \\\"followers_url\\\": \\\"https://api.github.com/users/kszucs/followers\\\",\\n+ \\\"following_url\\\": \\\"https://api.github.com/users/kszucs/following{/other_user}\\\",\\n+ \\\"gists_url\\\": \\\"https://api.github.com/users/kszucs/gists{/gist_id}\\\",\\n+ \\\"gravatar_id\\\": \\\"\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/kszucs\\\",\\n+ \\\"id\\\": 961747,\\n+ \\\"login\\\": \\\"kszucs\\\",\\n+ \\\"node_id\\\": \\\"MDQ6VXNlcjk2MTc0Nw==\\\",\\n+ \\\"organizations_url\\\": \\\"https://api.github.com/users/kszucs/orgs\\\",\\n+ \\\"received_events_url\\\": \\\"https://api.github.com/users/kszucs/received_events\\\",\\n+ \\\"repos_url\\\": \\\"https://api.github.com/users/kszucs/repos\\\",\\n+ \\\"site_admin\\\": false,\\n+ \\\"starred_url\\\": \\\"https://api.github.com/users/kszucs/starred{/owner}{/repo}\\\",\\n+ \\\"subscriptions_url\\\": \\\"https://api.github.com/users/kszucs/subscriptions\\\",\\n+ \\\"type\\\": \\\"User\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/users/kszucs\\\"\\n+ }\\n+ },\\n+ \\\"organization\\\": {\\n+ \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/46514972?v=4\\\",\\n+ \\\"description\\\": \\\"Innovation lab for open source data science tools, powered by Apache Arrow\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/orgs/ursa-labs/events\\\",\\n+ \\\"hooks_url\\\": \\\"https://api.github.com/orgs/ursa-labs/hooks\\\",\\n+ \\\"id\\\": 46514972,\\n+ \\\"issues_url\\\": \\\"https://api.github.com/orgs/ursa-labs/issues\\\",\\n+ \\\"login\\\": \\\"ursa-labs\\\",\\n+ \\\"members_url\\\": \\\"https://api.github.com/orgs/ursa-labs/members{/member}\\\",\\n+ \\\"node_id\\\": \\\"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\\\",\\n+ \\\"public_members_url\\\": \\\"https://api.github.com/orgs/ursa-labs/public_members{/member}\\\",\\n+ \\\"repos_url\\\": \\\"https://api.github.com/orgs/ursa-labs/repos\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/orgs/ursa-labs\\\"\\n+ },\\n+ \\\"repository\\\": {\\n+ \\\"archive_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\\\",\\n+ \\\"archived\\\": false,\\n+ \\\"assignees_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\\\",\\n+ \\\"blobs_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\\\",\\n+ \\\"branches_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\\\",\\n+ \\\"clone_url\\\": \\\"https://github.com/ursa-labs/ursabot.git\\\",\\n+ \\\"collaborators_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\\\",\\n+ \\\"comments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\\\",\\n+ \\\"commits_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\\\",\\n+ \\\"compare_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\\\",\\n+ \\\"contents_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\\\",\\n+ \\\"contributors_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/contributors\\\",\\n+ \\\"created_at\\\": \\\"2019-02-04T15:40:31Z\\\",\\n+ \\\"default_branch\\\": \\\"master\\\",\\n+ \\\"deployments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/deployments\\\",\\n+ \\\"description\\\": null,\\n+ \\\"disabled\\\": false,\\n+ \\\"downloads_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/downloads\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/events\\\",\\n+ \\\"fork\\\": false,\\n+ \\\"forks\\\": 0,\\n+ \\\"forks_count\\\": 0,\\n+ \\\"forks_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/forks\\\",\\n+ \\\"full_name\\\": \\\"ursa-labs/ursabot\\\",\\n+ \\\"git_commits_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\\\",\\n+ \\\"git_refs_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\\\",\\n+ \\\"git_tags_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\\\",\\n+ \\\"git_url\\\": \\\"git://github.com/ursa-labs/ursabot.git\\\",\\n+ \\\"has_downloads\\\": true,\\n+ \\\"has_issues\\\": true,\\n+ \\\"has_pages\\\": false,\\n+ \\\"has_projects\\\": true,\\n+ \\\"has_wiki\\\": true,\\n+ \\\"homepage\\\": null,\\n+ \\\"hooks_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/hooks\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot\\\",\\n+ \\\"id\\\": 169101701,\\n+ \\\"issue_comment_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\\\",\\n+ \\\"issue_events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\\\",\\n+ \\\"issues_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\\\",\\n+ \\\"keys_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\\\",\\n+ \\\"labels_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\\\",\\n+ \\\"language\\\": \\\"Jupyter Notebook\\\",\\n+ \\\"languages_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/languages\\\",\\n+ \\\"license\\\": null,\\n+ \\\"merges_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/merges\\\",\\n+ \\\"milestones_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\\\",\\n+ \\\"mirror_url\\\": null,\\n+ \\\"name\\\": \\\"ursabot\\\",\\n+ \\\"node_id\\\": \\\"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\\\",\\n+ \\\"notifications_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\\\",\\n+ \\\"open_issues\\\": 19,\\n+ \\\"open_issues_count\\\": 19,\\n+ \\\"owner\\\": {\\n+ \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/46514972?v=4\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/users/ursa-labs/events{/privacy}\\\",\\n+ \\\"followers_url\\\": \\\"https://api.github.com/users/ursa-labs/followers\\\",\\n+ \\\"following_url\\\": \\\"https://api.github.com/users/ursa-labs/following{/other_user}\\\",\\n+ \\\"gists_url\\\": \\\"https://api.github.com/users/ursa-labs/gists{/gist_id}\\\",\\n+ \\\"gravatar_id\\\": \\\"\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursa-labs\\\",\\n+ \\\"id\\\": 46514972,\\n+ \\\"login\\\": \\\"ursa-labs\\\",\\n+ \\\"node_id\\\": \\\"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\\\",\\n+ \\\"organizations_url\\\": \\\"https://api.github.com/users/ursa-labs/orgs\\\",\\n+ \\\"received_events_url\\\": \\\"https://api.github.com/users/ursa-labs/received_events\\\",\\n+ \\\"repos_url\\\": \\\"https://api.github.com/users/ursa-labs/repos\\\",\\n+ \\\"site_admin\\\": false,\\n+ \\\"starred_url\\\": \\\"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\\\",\\n+ \\\"subscriptions_url\\\": \\\"https://api.github.com/users/ursa-labs/subscriptions\\\",\\n+ \\\"type\\\": \\\"Organization\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/users/ursa-labs\\\"\\n+ },\\n+ \\\"private\\\": false,\\n+ \\\"pulls_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\\\",\\n+ \\\"pushed_at\\\": \\\"2019-04-05T11:22:16Z\\\",\\n+ \\\"releases_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\\\",\\n+ \\\"size\\\": 892,\\n+ \\\"ssh_url\\\": \\\"git@github.com:ursa-labs/ursabot.git\\\",\\n+ \\\"stargazers_count\\\": 1,\\n+ \\\"stargazers_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/stargazers\\\",\\n+ \\\"statuses_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\\\",\\n+ \\\"subscribers_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/subscribers\\\",\\n+ \\\"subscription_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/subscription\\\",\\n+ \\\"svn_url\\\": \\\"https://github.com/ursa-labs/ursabot\\\",\\n+ \\\"tags_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/tags\\\",\\n+ \\\"teams_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/teams\\\",\\n+ \\\"trees_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\\\",\\n+ \\\"updated_at\\\": \\\"2019-04-04T17:49:10Z\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot\\\",\\n+ \\\"watchers\\\": 1,\\n+ \\\"watchers_count\\\": 1\\n+ },\\n+ \\\"sender\\\": {\\n+ \\\"avatar_url\\\": \\\"https://avatars1.githubusercontent.com/u/961747?v=4\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/users/kszucs/events{/privacy}\\\",\\n+ \\\"followers_url\\\": \\\"https://api.github.com/users/kszucs/followers\\\",\\n+ \\\"following_url\\\": \\\"https://api.github.com/users/kszucs/following{/other_user}\\\",\\n+ \\\"gists_url\\\": \\\"https://api.github.com/users/kszucs/gists{/gist_id}\\\",\\n+ \\\"gravatar_id\\\": \\\"\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/kszucs\\\",\\n+ \\\"id\\\": 961747,\\n+ \\\"login\\\": \\\"kszucs\\\",\\n+ \\\"node_id\\\": \\\"MDQ6VXNlcjk2MTc0Nw==\\\",\\n+ \\\"organizations_url\\\": \\\"https://api.github.com/users/kszucs/orgs\\\",\\n+ \\\"received_events_url\\\": \\\"https://api.github.com/users/kszucs/received_events\\\",\\n+ \\\"repos_url\\\": \\\"https://api.github.com/users/kszucs/repos\\\",\\n+ \\\"site_admin\\\": false,\\n+ \\\"starred_url\\\": \\\"https://api.github.com/users/kszucs/starred{/owner}{/repo}\\\",\\n+ \\\"subscriptions_url\\\": \\\"https://api.github.com/users/kszucs/subscriptions\\\",\\n+ \\\"type\\\": \\\"User\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/users/kszucs\\\"\\n+ }\\n+}\"\n+ },\n+ {\n+ \"sha\": \"2770e29ba9086394455315e590c0b433d08e437e\",\n+ \"filename\": \"ursabot/tests/fixtures/issue-comment-with-empty-command.json\",\n+ \"status\": \"added\",\n+ \"additions\": 212,\n+ \"deletions\": 0,\n+ \"changes\": 212,\n+ \"blob_url\": \"https://github.com/ursa-labs/ursabot/blob/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-with-empty-command.json\",\n+ \"raw_url\": \"https://github.com/ursa-labs/ursabot/raw/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-with-empty-command.json\",\n+ \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-with-empty-command.json?ref=2705da2b616b98fa6010a25813c5a7a27456f71d\",\n+ \"patch\": \"@@ -0,0 +1,212 @@\\n+{\\n+ \\\"action\\\": \\\"created\\\",\\n+ \\\"comment\\\": {\\n+ \\\"author_association\\\": \\\"MEMBER\\\",\\n+ \\\"body\\\": \\\"@ursabot \\\",\\n+ \\\"created_at\\\": \\\"2019-04-05T11:35:46Z\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480243811\\\",\\n+ \\\"id\\\": 480243811,\\n+ \\\"issue_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26\\\",\\n+ \\\"node_id\\\": \\\"MDEyOklzc3VlQ29tbWVudDQ4MDI0MzgxMQ==\\\",\\n+ \\\"updated_at\\\": \\\"2019-04-05T11:35:46Z\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480243811\\\",\\n+ \\\"user\\\": {\\n+ \\\"avatar_url\\\": \\\"https://avatars1.githubusercontent.com/u/961747?v=4\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/users/kszucs/events{/privacy}\\\",\\n+ \\\"followers_url\\\": \\\"https://api.github.com/users/kszucs/followers\\\",\\n+ \\\"following_url\\\": \\\"https://api.github.com/users/kszucs/following{/other_user}\\\",\\n+ \\\"gists_url\\\": \\\"https://api.github.com/users/kszucs/gists{/gist_id}\\\",\\n+ \\\"gravatar_id\\\": \\\"\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/kszucs\\\",\\n+ \\\"id\\\": 961747,\\n+ \\\"login\\\": \\\"kszucs\\\",\\n+ \\\"node_id\\\": \\\"MDQ6VXNlcjk2MTc0Nw==\\\",\\n+ \\\"organizations_url\\\": \\\"https://api.github.com/users/kszucs/orgs\\\",\\n+ \\\"received_events_url\\\": \\\"https://api.github.com/users/kszucs/received_events\\\",\\n+ \\\"repos_url\\\": \\\"https://api.github.com/users/kszucs/repos\\\",\\n+ \\\"site_admin\\\": false,\\n+ \\\"starred_url\\\": \\\"https://api.github.com/users/kszucs/starred{/owner}{/repo}\\\",\\n+ \\\"subscriptions_url\\\": \\\"https://api.github.com/users/kszucs/subscriptions\\\",\\n+ \\\"type\\\": \\\"User\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/users/kszucs\\\"\\n+ }\\n+ },\\n+ \\\"issue\\\": {\\n+ \\\"assignee\\\": null,\\n+ \\\"assignees\\\": [],\\n+ \\\"author_association\\\": \\\"MEMBER\\\",\\n+ \\\"body\\\": \\\"\\\",\\n+ \\\"closed_at\\\": null,\\n+ \\\"comments\\\": 1,\\n+ \\\"comments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments\\\",\\n+ \\\"created_at\\\": \\\"2019-04-05T11:22:15Z\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26/events\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26\\\",\\n+ \\\"id\\\": 429706959,\\n+ \\\"labels\\\": [],\\n+ \\\"labels_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}\\\",\\n+ \\\"locked\\\": false,\\n+ \\\"milestone\\\": null,\\n+ \\\"node_id\\\": \\\"MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy\\\",\\n+ \\\"number\\\": 26,\\n+ \\\"pull_request\\\": {\\n+ \\\"diff_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26.diff\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26\\\",\\n+ \\\"patch_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26.patch\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/pulls/26\\\"\\n+ },\\n+ \\\"repository_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot\\\",\\n+ \\\"state\\\": \\\"open\\\",\\n+ \\\"title\\\": \\\"Unittests for GithubHook\\\",\\n+ \\\"updated_at\\\": \\\"2019-04-05T11:35:46Z\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26\\\",\\n+ \\\"user\\\": {\\n+ \\\"avatar_url\\\": \\\"https://avatars1.githubusercontent.com/u/961747?v=4\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/users/kszucs/events{/privacy}\\\",\\n+ \\\"followers_url\\\": \\\"https://api.github.com/users/kszucs/followers\\\",\\n+ \\\"following_url\\\": \\\"https://api.github.com/users/kszucs/following{/other_user}\\\",\\n+ \\\"gists_url\\\": \\\"https://api.github.com/users/kszucs/gists{/gist_id}\\\",\\n+ \\\"gravatar_id\\\": \\\"\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/kszucs\\\",\\n+ \\\"id\\\": 961747,\\n+ \\\"login\\\": \\\"kszucs\\\",\\n+ \\\"node_id\\\": \\\"MDQ6VXNlcjk2MTc0Nw==\\\",\\n+ \\\"organizations_url\\\": \\\"https://api.github.com/users/kszucs/orgs\\\",\\n+ \\\"received_events_url\\\": \\\"https://api.github.com/users/kszucs/received_events\\\",\\n+ \\\"repos_url\\\": \\\"https://api.github.com/users/kszucs/repos\\\",\\n+ \\\"site_admin\\\": false,\\n+ \\\"starred_url\\\": \\\"https://api.github.com/users/kszucs/starred{/owner}{/repo}\\\",\\n+ \\\"subscriptions_url\\\": \\\"https://api.github.com/users/kszucs/subscriptions\\\",\\n+ \\\"type\\\": \\\"User\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/users/kszucs\\\"\\n+ }\\n+ },\\n+ \\\"organization\\\": {\\n+ \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/46514972?v=4\\\",\\n+ \\\"description\\\": \\\"Innovation lab for open source data science tools, powered by Apache Arrow\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/orgs/ursa-labs/events\\\",\\n+ \\\"hooks_url\\\": \\\"https://api.github.com/orgs/ursa-labs/hooks\\\",\\n+ \\\"id\\\": 46514972,\\n+ \\\"issues_url\\\": \\\"https://api.github.com/orgs/ursa-labs/issues\\\",\\n+ \\\"login\\\": \\\"ursa-labs\\\",\\n+ \\\"members_url\\\": \\\"https://api.github.com/orgs/ursa-labs/members{/member}\\\",\\n+ \\\"node_id\\\": \\\"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\\\",\\n+ \\\"public_members_url\\\": \\\"https://api.github.com/orgs/ursa-labs/public_members{/member}\\\",\\n+ \\\"repos_url\\\": \\\"https://api.github.com/orgs/ursa-labs/repos\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/orgs/ursa-labs\\\"\\n+ },\\n+ \\\"repository\\\": {\\n+ \\\"archive_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\\\",\\n+ \\\"archived\\\": false,\\n+ \\\"assignees_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\\\",\\n+ \\\"blobs_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\\\",\\n+ \\\"branches_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\\\",\\n+ \\\"clone_url\\\": \\\"https://github.com/ursa-labs/ursabot.git\\\",\\n+ \\\"collaborators_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\\\",\\n+ \\\"comments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\\\",\\n+ \\\"commits_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\\\",\\n+ \\\"compare_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\\\",\\n+ \\\"contents_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\\\",\\n+ \\\"contributors_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/contributors\\\",\\n+ \\\"created_at\\\": \\\"2019-02-04T15:40:31Z\\\",\\n+ \\\"default_branch\\\": \\\"master\\\",\\n+ \\\"deployments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/deployments\\\",\\n+ \\\"description\\\": null,\\n+ \\\"disabled\\\": false,\\n+ \\\"downloads_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/downloads\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/events\\\",\\n+ \\\"fork\\\": false,\\n+ \\\"forks\\\": 0,\\n+ \\\"forks_count\\\": 0,\\n+ \\\"forks_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/forks\\\",\\n+ \\\"full_name\\\": \\\"ursa-labs/ursabot\\\",\\n+ \\\"git_commits_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\\\",\\n+ \\\"git_refs_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\\\",\\n+ \\\"git_tags_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\\\",\\n+ \\\"git_url\\\": \\\"git://github.com/ursa-labs/ursabot.git\\\",\\n+ \\\"has_downloads\\\": true,\\n+ \\\"has_issues\\\": true,\\n+ \\\"has_pages\\\": false,\\n+ \\\"has_projects\\\": true,\\n+ \\\"has_wiki\\\": true,\\n+ \\\"homepage\\\": null,\\n+ \\\"hooks_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/hooks\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot\\\",\\n+ \\\"id\\\": 169101701,\\n+ \\\"issue_comment_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\\\",\\n+ \\\"issue_events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\\\",\\n+ \\\"issues_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\\\",\\n+ \\\"keys_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\\\",\\n+ \\\"labels_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\\\",\\n+ \\\"language\\\": \\\"Jupyter Notebook\\\",\\n+ \\\"languages_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/languages\\\",\\n+ \\\"license\\\": null,\\n+ \\\"merges_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/merges\\\",\\n+ \\\"milestones_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\\\",\\n+ \\\"mirror_url\\\": null,\\n+ \\\"name\\\": \\\"ursabot\\\",\\n+ \\\"node_id\\\": \\\"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\\\",\\n+ \\\"notifications_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\\\",\\n+ \\\"open_issues\\\": 19,\\n+ \\\"open_issues_count\\\": 19,\\n+ \\\"owner\\\": {\\n+ \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/46514972?v=4\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/users/ursa-labs/events{/privacy}\\\",\\n+ \\\"followers_url\\\": \\\"https://api.github.com/users/ursa-labs/followers\\\",\\n+ \\\"following_url\\\": \\\"https://api.github.com/users/ursa-labs/following{/other_user}\\\",\\n+ \\\"gists_url\\\": \\\"https://api.github.com/users/ursa-labs/gists{/gist_id}\\\",\\n+ \\\"gravatar_id\\\": \\\"\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursa-labs\\\",\\n+ \\\"id\\\": 46514972,\\n+ \\\"login\\\": \\\"ursa-labs\\\",\\n+ \\\"node_id\\\": \\\"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\\\",\\n+ \\\"organizations_url\\\": \\\"https://api.github.com/users/ursa-labs/orgs\\\",\\n+ \\\"received_events_url\\\": \\\"https://api.github.com/users/ursa-labs/received_events\\\",\\n+ \\\"repos_url\\\": \\\"https://api.github.com/users/ursa-labs/repos\\\",\\n+ \\\"site_admin\\\": false,\\n+ \\\"starred_url\\\": \\\"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\\\",\\n+ \\\"subscriptions_url\\\": \\\"https://api.github.com/users/ursa-labs/subscriptions\\\",\\n+ \\\"type\\\": \\\"Organization\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/users/ursa-labs\\\"\\n+ },\\n+ \\\"private\\\": false,\\n+ \\\"pulls_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\\\",\\n+ \\\"pushed_at\\\": \\\"2019-04-05T11:22:16Z\\\",\\n+ \\\"releases_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\\\",\\n+ \\\"size\\\": 892,\\n+ \\\"ssh_url\\\": \\\"git@github.com:ursa-labs/ursabot.git\\\",\\n+ \\\"stargazers_count\\\": 1,\\n+ \\\"stargazers_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/stargazers\\\",\\n+ \\\"statuses_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\\\",\\n+ \\\"subscribers_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/subscribers\\\",\\n+ \\\"subscription_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/subscription\\\",\\n+ \\\"svn_url\\\": \\\"https://github.com/ursa-labs/ursabot\\\",\\n+ \\\"tags_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/tags\\\",\\n+ \\\"teams_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/teams\\\",\\n+ \\\"trees_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\\\",\\n+ \\\"updated_at\\\": \\\"2019-04-04T17:49:10Z\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot\\\",\\n+ \\\"watchers\\\": 1,\\n+ \\\"watchers_count\\\": 1\\n+ },\\n+ \\\"sender\\\": {\\n+ \\\"avatar_url\\\": \\\"https://avatars1.githubusercontent.com/u/961747?v=4\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/users/kszucs/events{/privacy}\\\",\\n+ \\\"followers_url\\\": \\\"https://api.github.com/users/kszucs/followers\\\",\\n+ \\\"following_url\\\": \\\"https://api.github.com/users/kszucs/following{/other_user}\\\",\\n+ \\\"gists_url\\\": \\\"https://api.github.com/users/kszucs/gists{/gist_id}\\\",\\n+ \\\"gravatar_id\\\": \\\"\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/kszucs\\\",\\n+ \\\"id\\\": 961747,\\n+ \\\"login\\\": \\\"kszucs\\\",\\n+ \\\"node_id\\\": \\\"MDQ6VXNlcjk2MTc0Nw==\\\",\\n+ \\\"organizations_url\\\": \\\"https://api.github.com/users/kszucs/orgs\\\",\\n+ \\\"received_events_url\\\": \\\"https://api.github.com/users/kszucs/received_events\\\",\\n+ \\\"repos_url\\\": \\\"https://api.github.com/users/kszucs/repos\\\",\\n+ \\\"site_admin\\\": false,\\n+ \\\"starred_url\\\": \\\"https://api.github.com/users/kszucs/starred{/owner}{/repo}\\\",\\n+ \\\"subscriptions_url\\\": \\\"https://api.github.com/users/kszucs/subscriptions\\\",\\n+ \\\"type\\\": \\\"User\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/users/kszucs\\\"\\n+ }\\n+}\"\n+ },\n+ {\n+ \"sha\": \"80ff46510a2f39ae60f7c3a98e5fdaef8e688784\",\n+ \"filename\": \"ursabot/tests/fixtures/issue-comment-without-pull-request.json\",\n+ \"status\": \"added\",\n+ \"additions\": 206,\n+ \"deletions\": 0,\n+ \"changes\": 206,\n+ \"blob_url\": \"https://github.com/ursa-labs/ursabot/blob/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-without-pull-request.json\",\n+ \"raw_url\": \"https://github.com/ursa-labs/ursabot/raw/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-without-pull-request.json\",\n+ \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-without-pull-request.json?ref=2705da2b616b98fa6010a25813c5a7a27456f71d\",\n+ \"patch\": \"@@ -0,0 +1,206 @@\\n+{\\n+ \\\"action\\\": \\\"created\\\",\\n+ \\\"comment\\\": {\\n+ \\\"author_association\\\": \\\"NONE\\\",\\n+ \\\"body\\\": \\\"Ursabot only listens to pull request comments!\\\",\\n+ \\\"created_at\\\": \\\"2019-04-05T11:53:43Z\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot/issues/19#issuecomment-480248217\\\",\\n+ \\\"id\\\": 480248217,\\n+ \\\"issue_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/19\\\",\\n+ \\\"node_id\\\": \\\"MDEyOklzc3VlQ29tbWVudDQ4MDI0ODIxNw==\\\",\\n+ \\\"updated_at\\\": \\\"2019-04-05T11:53:43Z\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480248217\\\",\\n+ \\\"user\\\": {\\n+ \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/49275095?v=4\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/users/ursabot/events{/privacy}\\\",\\n+ \\\"followers_url\\\": \\\"https://api.github.com/users/ursabot/followers\\\",\\n+ \\\"following_url\\\": \\\"https://api.github.com/users/ursabot/following{/other_user}\\\",\\n+ \\\"gists_url\\\": \\\"https://api.github.com/users/ursabot/gists{/gist_id}\\\",\\n+ \\\"gravatar_id\\\": \\\"\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursabot\\\",\\n+ \\\"id\\\": 49275095,\\n+ \\\"login\\\": \\\"ursabot\\\",\\n+ \\\"node_id\\\": \\\"MDQ6VXNlcjQ5Mjc1MDk1\\\",\\n+ \\\"organizations_url\\\": \\\"https://api.github.com/users/ursabot/orgs\\\",\\n+ \\\"received_events_url\\\": \\\"https://api.github.com/users/ursabot/received_events\\\",\\n+ \\\"repos_url\\\": \\\"https://api.github.com/users/ursabot/repos\\\",\\n+ \\\"site_admin\\\": false,\\n+ \\\"starred_url\\\": \\\"https://api.github.com/users/ursabot/starred{/owner}{/repo}\\\",\\n+ \\\"subscriptions_url\\\": \\\"https://api.github.com/users/ursabot/subscriptions\\\",\\n+ \\\"type\\\": \\\"User\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/users/ursabot\\\"\\n+ }\\n+ },\\n+ \\\"issue\\\": {\\n+ \\\"assignee\\\": null,\\n+ \\\"assignees\\\": [],\\n+ \\\"author_association\\\": \\\"MEMBER\\\",\\n+ \\\"body\\\": \\\"\\\",\\n+ \\\"closed_at\\\": null,\\n+ \\\"comments\\\": 4,\\n+ \\\"comments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/19/comments\\\",\\n+ \\\"created_at\\\": \\\"2019-04-02T09:56:41Z\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/19/events\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot/issues/19\\\",\\n+ \\\"id\\\": 428131685,\\n+ \\\"labels\\\": [],\\n+ \\\"labels_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/19/labels{/name}\\\",\\n+ \\\"locked\\\": false,\\n+ \\\"milestone\\\": null,\\n+ \\\"node_id\\\": \\\"MDU6SXNzdWU0MjgxMzE2ODU=\\\",\\n+ \\\"number\\\": 19,\\n+ \\\"repository_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot\\\",\\n+ \\\"state\\\": \\\"open\\\",\\n+ \\\"title\\\": \\\"Build ursabot itself via ursabot\\\",\\n+ \\\"updated_at\\\": \\\"2019-04-05T11:53:43Z\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/19\\\",\\n+ \\\"user\\\": {\\n+ \\\"avatar_url\\\": \\\"https://avatars1.githubusercontent.com/u/961747?v=4\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/users/kszucs/events{/privacy}\\\",\\n+ \\\"followers_url\\\": \\\"https://api.github.com/users/kszucs/followers\\\",\\n+ \\\"following_url\\\": \\\"https://api.github.com/users/kszucs/following{/other_user}\\\",\\n+ \\\"gists_url\\\": \\\"https://api.github.com/users/kszucs/gists{/gist_id}\\\",\\n+ \\\"gravatar_id\\\": \\\"\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/kszucs\\\",\\n+ \\\"id\\\": 961747,\\n+ \\\"login\\\": \\\"kszucs\\\",\\n+ \\\"node_id\\\": \\\"MDQ6VXNlcjk2MTc0Nw==\\\",\\n+ \\\"organizations_url\\\": \\\"https://api.github.com/users/kszucs/orgs\\\",\\n+ \\\"received_events_url\\\": \\\"https://api.github.com/users/kszucs/received_events\\\",\\n+ \\\"repos_url\\\": \\\"https://api.github.com/users/kszucs/repos\\\",\\n+ \\\"site_admin\\\": false,\\n+ \\\"starred_url\\\": \\\"https://api.github.com/users/kszucs/starred{/owner}{/repo}\\\",\\n+ \\\"subscriptions_url\\\": \\\"https://api.github.com/users/kszucs/subscriptions\\\",\\n+ \\\"type\\\": \\\"User\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/users/kszucs\\\"\\n+ }\\n+ },\\n+ \\\"organization\\\": {\\n+ \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/46514972?v=4\\\",\\n+ \\\"description\\\": \\\"Innovation lab for open source data science tools, powered by Apache Arrow\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/orgs/ursa-labs/events\\\",\\n+ \\\"hooks_url\\\": \\\"https://api.github.com/orgs/ursa-labs/hooks\\\",\\n+ \\\"id\\\": 46514972,\\n+ \\\"issues_url\\\": \\\"https://api.github.com/orgs/ursa-labs/issues\\\",\\n+ \\\"login\\\": \\\"ursa-labs\\\",\\n+ \\\"members_url\\\": \\\"https://api.github.com/orgs/ursa-labs/members{/member}\\\",\\n+ \\\"node_id\\\": \\\"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\\\",\\n+ \\\"public_members_url\\\": \\\"https://api.github.com/orgs/ursa-labs/public_members{/member}\\\",\\n+ \\\"repos_url\\\": \\\"https://api.github.com/orgs/ursa-labs/repos\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/orgs/ursa-labs\\\"\\n+ },\\n+ \\\"repository\\\": {\\n+ \\\"archive_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\\\",\\n+ \\\"archived\\\": false,\\n+ \\\"assignees_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\\\",\\n+ \\\"blobs_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\\\",\\n+ \\\"branches_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\\\",\\n+ \\\"clone_url\\\": \\\"https://github.com/ursa-labs/ursabot.git\\\",\\n+ \\\"collaborators_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\\\",\\n+ \\\"comments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\\\",\\n+ \\\"commits_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\\\",\\n+ \\\"compare_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\\\",\\n+ \\\"contents_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\\\",\\n+ \\\"contributors_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/contributors\\\",\\n+ \\\"created_at\\\": \\\"2019-02-04T15:40:31Z\\\",\\n+ \\\"default_branch\\\": \\\"master\\\",\\n+ \\\"deployments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/deployments\\\",\\n+ \\\"description\\\": null,\\n+ \\\"disabled\\\": false,\\n+ \\\"downloads_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/downloads\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/events\\\",\\n+ \\\"fork\\\": false,\\n+ \\\"forks\\\": 0,\\n+ \\\"forks_count\\\": 0,\\n+ \\\"forks_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/forks\\\",\\n+ \\\"full_name\\\": \\\"ursa-labs/ursabot\\\",\\n+ \\\"git_commits_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\\\",\\n+ \\\"git_refs_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\\\",\\n+ \\\"git_tags_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\\\",\\n+ \\\"git_url\\\": \\\"git://github.com/ursa-labs/ursabot.git\\\",\\n+ \\\"has_downloads\\\": true,\\n+ \\\"has_issues\\\": true,\\n+ \\\"has_pages\\\": false,\\n+ \\\"has_projects\\\": true,\\n+ \\\"has_wiki\\\": true,\\n+ \\\"homepage\\\": null,\\n+ \\\"hooks_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/hooks\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot\\\",\\n+ \\\"id\\\": 169101701,\\n+ \\\"issue_comment_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\\\",\\n+ \\\"issue_events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\\\",\\n+ \\\"issues_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\\\",\\n+ \\\"keys_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\\\",\\n+ \\\"labels_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\\\",\\n+ \\\"language\\\": \\\"Jupyter Notebook\\\",\\n+ \\\"languages_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/languages\\\",\\n+ \\\"license\\\": null,\\n+ \\\"merges_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/merges\\\",\\n+ \\\"milestones_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\\\",\\n+ \\\"mirror_url\\\": null,\\n+ \\\"name\\\": \\\"ursabot\\\",\\n+ \\\"node_id\\\": \\\"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\\\",\\n+ \\\"notifications_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\\\",\\n+ \\\"open_issues\\\": 19,\\n+ \\\"open_issues_count\\\": 19,\\n+ \\\"owner\\\": {\\n+ \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/46514972?v=4\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/users/ursa-labs/events{/privacy}\\\",\\n+ \\\"followers_url\\\": \\\"https://api.github.com/users/ursa-labs/followers\\\",\\n+ \\\"following_url\\\": \\\"https://api.github.com/users/ursa-labs/following{/other_user}\\\",\\n+ \\\"gists_url\\\": \\\"https://api.github.com/users/ursa-labs/gists{/gist_id}\\\",\\n+ \\\"gravatar_id\\\": \\\"\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursa-labs\\\",\\n+ \\\"id\\\": 46514972,\\n+ \\\"login\\\": \\\"ursa-labs\\\",\\n+ \\\"node_id\\\": \\\"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\\\",\\n+ \\\"organizations_url\\\": \\\"https://api.github.com/users/ursa-labs/orgs\\\",\\n+ \\\"received_events_url\\\": \\\"https://api.github.com/users/ursa-labs/received_events\\\",\\n+ \\\"repos_url\\\": \\\"https://api.github.com/users/ursa-labs/repos\\\",\\n+ \\\"site_admin\\\": false,\\n+ \\\"starred_url\\\": \\\"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\\\",\\n+ \\\"subscriptions_url\\\": \\\"https://api.github.com/users/ursa-labs/subscriptions\\\",\\n+ \\\"type\\\": \\\"Organization\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/users/ursa-labs\\\"\\n+ },\\n+ \\\"private\\\": false,\\n+ \\\"pulls_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\\\",\\n+ \\\"pushed_at\\\": \\\"2019-04-05T11:22:16Z\\\",\\n+ \\\"releases_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\\\",\\n+ \\\"size\\\": 892,\\n+ \\\"ssh_url\\\": \\\"git@github.com:ursa-labs/ursabot.git\\\",\\n+ \\\"stargazers_count\\\": 1,\\n+ \\\"stargazers_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/stargazers\\\",\\n+ \\\"statuses_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\\\",\\n+ \\\"subscribers_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/subscribers\\\",\\n+ \\\"subscription_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/subscription\\\",\\n+ \\\"svn_url\\\": \\\"https://github.com/ursa-labs/ursabot\\\",\\n+ \\\"tags_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/tags\\\",\\n+ \\\"teams_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/teams\\\",\\n+ \\\"trees_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\\\",\\n+ \\\"updated_at\\\": \\\"2019-04-04T17:49:10Z\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot\\\",\\n+ \\\"watchers\\\": 1,\\n+ \\\"watchers_count\\\": 1\\n+ },\\n+ \\\"sender\\\": {\\n+ \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/49275095?v=4\\\",\\n+ \\\"events_url\\\": \\\"https://api.github.com/users/ursabot/events{/privacy}\\\",\\n+ \\\"followers_url\\\": \\\"https://api.github.com/users/ursabot/followers\\\",\\n+ \\\"following_url\\\": \\\"https://api.github.com/users/ursabot/following{/other_user}\\\",\\n+ \\\"gists_url\\\": \\\"https://api.github.com/users/ursabot/gists{/gist_id}\\\",\\n+ \\\"gravatar_id\\\": \\\"\\\",\\n+ \\\"html_url\\\": \\\"https://github.com/ursabot\\\",\\n+ \\\"id\\\": 49275095,\\n+ \\\"login\\\": \\\"ursabot\\\",\\n+ \\\"node_id\\\": \\\"MDQ6VXNlcjQ5Mjc1MDk1\\\",\\n+ \\\"organizations_url\\\": \\\"https://api.github.com/users/ursabot/orgs\\\",\\n+ \\\"received_events_url\\\": \\\"https://api.github.com/users/ursabot/received_events\\\",\\n+ \\\"repos_url\\\": \\\"https://api.github.com/users/ursabot/repos\\\",\\n+ \\\"site_admin\\\": false,\\n+ \\\"starred_url\\\": \\\"https://api.github.com/users/ursabot/starred{/owner}{/repo}\\\",\\n+ \\\"subscriptions_url\\\": \\\"https://api.github.com/users/ursabot/subscriptions\\\",\\n+ \\\"type\\\": \\\"User\\\",\\n+ \\\"url\\\": \\\"https://api.github.com/users/ursabot\\\"\\n+ }\\n+}\"\n+ },\n+ {\n+ \"sha\": \"c738bb0eb54c87ba0f23e97e827d77c2be74d0b6\",\n+ \"filename\": \"ursabot/tests/test_hooks.py\",\n+ \"status\": \"modified\",\n+ \"additions\": 4,\n+ \"deletions\": 4,\n+ \"changes\": 8,\n+ \"blob_url\": \"https://github.com/ursa-labs/ursabot/blob/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/test_hooks.py\",\n+ \"raw_url\": \"https://github.com/ursa-labs/ursabot/raw/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/test_hooks.py\",\n+ \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/test_hooks.py?ref=2705da2b616b98fa6010a25813c5a7a27456f71d\",\n+ \"patch\": \"@@ -54,7 +54,7 @@ class TestGithubHook(ChangeHookTestCase):\\n await self.request('ping', {})\\n assert len(self.hook.master.data.updates.changesAdded) == 0\\n \\n- @ensure_deferred\\n- async def test_issue_comment(self):\\n- payload = {}\\n- await self.request('issue_comment', payload)\\n+ # @ensure_deferred\\n+ # async def test_issue_comment(self):\\n+ # payload = {}\\n+ # await self.request('issue_comment', payload)\"\n+ }\n+ ]\n+}" - }, - { - "sha": "ad061d7244b917e6ea3853698dc3bc2a8c9c6857", - "filename": "ursabot/tests/fixtures/pull-request-26.json", - "status": "added", - "additions": 335, - "deletions": 0, - "changes": 335, - "blob_url": "https://github.com/ursa-labs/ursabot/blob/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/fixtures/pull-request-26.json", - "raw_url": "https://github.com/ursa-labs/ursabot/raw/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/fixtures/pull-request-26.json", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/pull-request-26.json?ref=70267dee34884e4b972388e1b30d57f6248c58d0", - "patch": "@@ -0,0 +1,335 @@\n+{\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/26\",\n+ \"id\": 267785552,\n+ \"node_id\": \"MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy\",\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+ \"diff_url\": \"https://github.com/ursa-labs/ursabot/pull/26.diff\",\n+ \"patch_url\": \"https://github.com/ursa-labs/ursabot/pull/26.patch\",\n+ \"issue_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+ \"number\": 26,\n+ \"state\": \"open\",\n+ \"locked\": false,\n+ \"title\": \"Unittests for GithubHook\",\n+ \"user\": {\n+ \"login\": \"kszucs\",\n+ \"id\": 961747,\n+ \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+ \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+ \"gravatar_id\": \"\",\n+ \"url\": \"https://api.github.com/users/kszucs\",\n+ \"html_url\": \"https://github.com/kszucs\",\n+ \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+ \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+ \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+ \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+ \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+ \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+ \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+ \"type\": \"User\",\n+ \"site_admin\": false\n+ },\n+ \"body\": \"\",\n+ \"created_at\": \"2019-04-05T11:22:15Z\",\n+ \"updated_at\": \"2019-04-05T12:01:40Z\",\n+ \"closed_at\": null,\n+ \"merged_at\": null,\n+ \"merge_commit_sha\": \"cc5dc3606988b3824be54df779ed2028776113cb\",\n+ \"assignee\": null,\n+ \"assignees\": [\n+\n+ ],\n+ \"requested_reviewers\": [\n+\n+ ],\n+ \"requested_teams\": [\n+\n+ ],\n+ \"labels\": [\n+\n+ ],\n+ \"milestone\": null,\n+ \"commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/26/commits\",\n+ \"review_comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/26/comments\",\n+ \"review_comment_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/comments{/number}\",\n+ \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments\",\n+ \"statuses_url\": \"https://api.github.com/repos/ursa-labs/ursabot/statuses/2705da2b616b98fa6010a25813c5a7a27456f71d\",\n+ \"head\": {\n+ \"label\": \"ursa-labs:test-hook\",\n+ \"ref\": \"test-hook\",\n+ \"sha\": \"2705da2b616b98fa6010a25813c5a7a27456f71d\",\n+ \"user\": {\n+ \"login\": \"ursa-labs\",\n+ \"id\": 46514972,\n+ \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+ \"gravatar_id\": \"\",\n+ \"url\": \"https://api.github.com/users/ursa-labs\",\n+ \"html_url\": \"https://github.com/ursa-labs\",\n+ \"followers_url\": \"https://api.github.com/users/ursa-labs/followers\",\n+ \"following_url\": \"https://api.github.com/users/ursa-labs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/ursa-labs/gists{/gist_id}\",\n+ \"starred_url\": \"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/ursa-labs/subscriptions\",\n+ \"organizations_url\": \"https://api.github.com/users/ursa-labs/orgs\",\n+ \"repos_url\": \"https://api.github.com/users/ursa-labs/repos\",\n+ \"events_url\": \"https://api.github.com/users/ursa-labs/events{/privacy}\",\n+ \"received_events_url\": \"https://api.github.com/users/ursa-labs/received_events\",\n+ \"type\": \"Organization\",\n+ \"site_admin\": false\n+ },\n+ \"repo\": {\n+ \"id\": 169101701,\n+ \"node_id\": \"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\",\n+ \"name\": \"ursabot\",\n+ \"full_name\": \"ursa-labs/ursabot\",\n+ \"private\": false,\n+ \"owner\": {\n+ \"login\": \"ursa-labs\",\n+ \"id\": 46514972,\n+ \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+ \"gravatar_id\": \"\",\n+ \"url\": \"https://api.github.com/users/ursa-labs\",\n+ \"html_url\": \"https://github.com/ursa-labs\",\n+ \"followers_url\": \"https://api.github.com/users/ursa-labs/followers\",\n+ \"following_url\": \"https://api.github.com/users/ursa-labs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/ursa-labs/gists{/gist_id}\",\n+ \"starred_url\": \"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/ursa-labs/subscriptions\",\n+ \"organizations_url\": \"https://api.github.com/users/ursa-labs/orgs\",\n+ \"repos_url\": \"https://api.github.com/users/ursa-labs/repos\",\n+ \"events_url\": \"https://api.github.com/users/ursa-labs/events{/privacy}\",\n+ \"received_events_url\": \"https://api.github.com/users/ursa-labs/received_events\",\n+ \"type\": \"Organization\",\n+ \"site_admin\": false\n+ },\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot\",\n+ \"description\": null,\n+ \"fork\": false,\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+ \"forks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/forks\",\n+ \"keys_url\": \"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\",\n+ \"collaborators_url\": \"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\",\n+ \"teams_url\": \"https://api.github.com/repos/ursa-labs/ursabot/teams\",\n+ \"hooks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/hooks\",\n+ \"issue_events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\",\n+ \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/events\",\n+ \"assignees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\",\n+ \"branches_url\": \"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\",\n+ \"tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/tags\",\n+ \"blobs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\",\n+ \"git_tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\",\n+ \"git_refs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\",\n+ \"trees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\",\n+ \"statuses_url\": \"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\",\n+ \"languages_url\": \"https://api.github.com/repos/ursa-labs/ursabot/languages\",\n+ \"stargazers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/stargazers\",\n+ \"contributors_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contributors\",\n+ \"subscribers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscribers\",\n+ \"subscription_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscription\",\n+ \"commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\",\n+ \"git_commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\",\n+ \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\",\n+ \"issue_comment_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\",\n+ \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\",\n+ \"compare_url\": \"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\",\n+ \"merges_url\": \"https://api.github.com/repos/ursa-labs/ursabot/merges\",\n+ \"archive_url\": \"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\",\n+ \"downloads_url\": \"https://api.github.com/repos/ursa-labs/ursabot/downloads\",\n+ \"issues_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\",\n+ \"pulls_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\",\n+ \"milestones_url\": \"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\",\n+ \"notifications_url\": \"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\",\n+ \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\",\n+ \"releases_url\": \"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\",\n+ \"deployments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/deployments\",\n+ \"created_at\": \"2019-02-04T15:40:31Z\",\n+ \"updated_at\": \"2019-04-04T17:49:10Z\",\n+ \"pushed_at\": \"2019-04-05T12:01:40Z\",\n+ \"git_url\": \"git://github.com/ursa-labs/ursabot.git\",\n+ \"ssh_url\": \"git@github.com:ursa-labs/ursabot.git\",\n+ \"clone_url\": \"https://github.com/ursa-labs/ursabot.git\",\n+ \"svn_url\": \"https://github.com/ursa-labs/ursabot\",\n+ \"homepage\": null,\n+ \"size\": 898,\n+ \"stargazers_count\": 1,\n+ \"watchers_count\": 1,\n+ \"language\": \"Jupyter Notebook\",\n+ \"has_issues\": true,\n+ \"has_projects\": true,\n+ \"has_downloads\": true,\n+ \"has_wiki\": true,\n+ \"has_pages\": false,\n+ \"forks_count\": 0,\n+ \"mirror_url\": null,\n+ \"archived\": false,\n+ \"disabled\": false,\n+ \"open_issues_count\": 19,\n+ \"license\": null,\n+ \"forks\": 0,\n+ \"open_issues\": 19,\n+ \"watchers\": 1,\n+ \"default_branch\": \"master\"\n+ }\n+ },\n+ \"base\": {\n+ \"label\": \"ursa-labs:master\",\n+ \"ref\": \"master\",\n+ \"sha\": \"a162ad254b589b924db47e057791191b39613fd5\",\n+ \"user\": {\n+ \"login\": \"ursa-labs\",\n+ \"id\": 46514972,\n+ \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+ \"gravatar_id\": \"\",\n+ \"url\": \"https://api.github.com/users/ursa-labs\",\n+ \"html_url\": \"https://github.com/ursa-labs\",\n+ \"followers_url\": \"https://api.github.com/users/ursa-labs/followers\",\n+ \"following_url\": \"https://api.github.com/users/ursa-labs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/ursa-labs/gists{/gist_id}\",\n+ \"starred_url\": \"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/ursa-labs/subscriptions\",\n+ \"organizations_url\": \"https://api.github.com/users/ursa-labs/orgs\",\n+ \"repos_url\": \"https://api.github.com/users/ursa-labs/repos\",\n+ \"events_url\": \"https://api.github.com/users/ursa-labs/events{/privacy}\",\n+ \"received_events_url\": \"https://api.github.com/users/ursa-labs/received_events\",\n+ \"type\": \"Organization\",\n+ \"site_admin\": false\n+ },\n+ \"repo\": {\n+ \"id\": 169101701,\n+ \"node_id\": \"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\",\n+ \"name\": \"ursabot\",\n+ \"full_name\": \"ursa-labs/ursabot\",\n+ \"private\": false,\n+ \"owner\": {\n+ \"login\": \"ursa-labs\",\n+ \"id\": 46514972,\n+ \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+ \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+ \"gravatar_id\": \"\",\n+ \"url\": \"https://api.github.com/users/ursa-labs\",\n+ \"html_url\": \"https://github.com/ursa-labs\",\n+ \"followers_url\": \"https://api.github.com/users/ursa-labs/followers\",\n+ \"following_url\": \"https://api.github.com/users/ursa-labs/following{/other_user}\",\n+ \"gists_url\": \"https://api.github.com/users/ursa-labs/gists{/gist_id}\",\n+ \"starred_url\": \"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\",\n+ \"subscriptions_url\": \"https://api.github.com/users/ursa-labs/subscriptions\",\n+ \"organizations_url\": \"https://api.github.com/users/ursa-labs/orgs\",\n+ \"repos_url\": \"https://api.github.com/users/ursa-labs/repos\",\n+ \"events_url\": \"https://api.github.com/users/ursa-labs/events{/privacy}\",\n+ \"received_events_url\": \"https://api.github.com/users/ursa-labs/received_events\",\n+ \"type\": \"Organization\",\n+ \"site_admin\": false\n+ },\n+ \"html_url\": \"https://github.com/ursa-labs/ursabot\",\n+ \"description\": null,\n+ \"fork\": false,\n+ \"url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+ \"forks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/forks\",\n+ \"keys_url\": \"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\",\n+ \"collaborators_url\": \"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\",\n+ \"teams_url\": \"https://api.github.com/repos/ursa-labs/ursabot/teams\",\n+ \"hooks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/hooks\",\n+ \"issue_events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\",\n+ \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/events\",\n+ \"assignees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\",\n+ \"branches_url\": \"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\",\n+ \"tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/tags\",\n+ \"blobs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\",\n+ \"git_tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\",\n+ \"git_refs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\",\n+ \"trees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\",\n+ \"statuses_url\": \"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\",\n+ \"languages_url\": \"https://api.github.com/repos/ursa-labs/ursabot/languages\",\n+ \"stargazers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/stargazers\",\n+ \"contributors_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contributors\",\n+ \"subscribers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscribers\",\n+ \"subscription_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscription\",\n+ \"commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\",\n+ \"git_commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\",\n+ \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\",\n+ \"issue_comment_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\",\n+ \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\",\n+ \"compare_url\": \"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\",\n+ \"merges_url\": \"https://api.github.com/repos/ursa-labs/ursabot/merges\",\n+ \"archive_url\": \"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\",\n+ \"downloads_url\": \"https://api.github.com/repos/ursa-labs/ursabot/downloads\",\n+ \"issues_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\",\n+ \"pulls_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\",\n+ \"milestones_url\": \"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\",\n+ \"notifications_url\": \"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\",\n+ \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\",\n+ \"releases_url\": \"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\",\n+ \"deployments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/deployments\",\n+ \"created_at\": \"2019-02-04T15:40:31Z\",\n+ \"updated_at\": \"2019-04-04T17:49:10Z\",\n+ \"pushed_at\": \"2019-04-05T12:01:40Z\",\n+ \"git_url\": \"git://github.com/ursa-labs/ursabot.git\",\n+ \"ssh_url\": \"git@github.com:ursa-labs/ursabot.git\",\n+ \"clone_url\": \"https://github.com/ursa-labs/ursabot.git\",\n+ \"svn_url\": \"https://github.com/ursa-labs/ursabot\",\n+ \"homepage\": null,\n+ \"size\": 898,\n+ \"stargazers_count\": 1,\n+ \"watchers_count\": 1,\n+ \"language\": \"Jupyter Notebook\",\n+ \"has_issues\": true,\n+ \"has_projects\": true,\n+ \"has_downloads\": true,\n+ \"has_wiki\": true,\n+ \"has_pages\": false,\n+ \"forks_count\": 0,\n+ \"mirror_url\": null,\n+ \"archived\": false,\n+ \"disabled\": false,\n+ \"open_issues_count\": 19,\n+ \"license\": null,\n+ \"forks\": 0,\n+ \"open_issues\": 19,\n+ \"watchers\": 1,\n+ \"default_branch\": \"master\"\n+ }\n+ },\n+ \"_links\": {\n+ \"self\": {\n+ \"href\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/26\"\n+ },\n+ \"html\": {\n+ \"href\": \"https://github.com/ursa-labs/ursabot/pull/26\"\n+ },\n+ \"issue\": {\n+ \"href\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\"\n+ },\n+ \"comments\": {\n+ \"href\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments\"\n+ },\n+ \"review_comments\": {\n+ \"href\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/26/comments\"\n+ },\n+ \"review_comment\": {\n+ \"href\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/comments{/number}\"\n+ },\n+ \"commits\": {\n+ \"href\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/26/commits\"\n+ },\n+ \"statuses\": {\n+ \"href\": \"https://api.github.com/repos/ursa-labs/ursabot/statuses/2705da2b616b98fa6010a25813c5a7a27456f71d\"\n+ }\n+ },\n+ \"author_association\": \"MEMBER\",\n+ \"merged\": false,\n+ \"mergeable\": true,\n+ \"rebaseable\": true,\n+ \"mergeable_state\": \"unstable\",\n+ \"merged_by\": null,\n+ \"comments\": 5,\n+ \"review_comments\": 0,\n+ \"maintainer_can_modify\": false,\n+ \"commits\": 2,\n+ \"additions\": 1124,\n+ \"deletions\": 0,\n+ \"changed_files\": 7\n+}" - }, - { - "sha": "e87b27d2d7b4956d15f7468488b96cf6a06686f4", - "filename": "ursabot/tests/test_hooks.py", - "status": "added", - "additions": 116, - "deletions": 0, - "changes": 116, - "blob_url": "https://github.com/ursa-labs/ursabot/blob/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/test_hooks.py", - "raw_url": "https://github.com/ursa-labs/ursabot/raw/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/test_hooks.py", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/test_hooks.py?ref=70267dee34884e4b972388e1b30d57f6248c58d0", - "patch": "@@ -0,0 +1,116 @@\n+import json\n+from pathlib import Path\n+from twisted.trial import unittest\n+\n+from buildbot.test.util.misc import TestReactorMixin\n+from buildbot.test.fake.httpclientservice import \\\n+ HTTPClientService as FakeHTTPClientService\n+from buildbot.test.unit.test_www_hooks_github import (\n+ _prepare_request, _prepare_github_change_hook)\n+\n+from ursabot.utils import ensure_deferred\n+from ursabot.hooks import GithubHook\n+\n+\n+class ChangeHookTestCase(unittest.TestCase, TestReactorMixin):\n+\n+ klass = None\n+\n+ @ensure_deferred\n+ async def setUp(self):\n+ self.setUpTestReactor()\n+\n+ assert self.klass is not None\n+ self.hook = _prepare_github_change_hook(self, **{'class': self.klass})\n+ self.master = self.hook.master\n+ self.http = await FakeHTTPClientService.getFakeService(\n+ self.master, self, 'https://api.github.com',\n+ headers={'User-Agent': 'Buildbot'}, debug=False, verify=False)\n+\n+ await self.master.startService()\n+\n+ @ensure_deferred\n+ async def tearDown(self):\n+ await self.master.stopService()\n+\n+ async def trigger(self, event, payload, headers=None, _secret=None):\n+ payload = json.dumps(payload).encode()\n+ request = _prepare_request(event, payload, _secret=_secret,\n+ headers=headers)\n+ await request.test_render(self.hook)\n+ return request\n+\n+ def load_fixture(self, name):\n+ path = Path(__file__).parent / 'fixtures' / f'{name}.json'\n+ with path.open('r') as fp:\n+ return json.load(fp)\n+\n+\n+class TestGithubHook(ChangeHookTestCase):\n+\n+ klass = GithubHook\n+\n+ @ensure_deferred\n+ async def test_ping(self):\n+ await self.trigger('ping', {})\n+ assert len(self.hook.master.data.updates.changesAdded) == 0\n+\n+ @ensure_deferred\n+ async def test_issue_comment_not_mentioning_ursabot(self):\n+ payload = self.load_fixture('issue-comment-not-mentioning-ursabot')\n+ await self.trigger('issue_comment', payload=payload)\n+ assert len(self.hook.master.data.updates.changesAdded) == 0\n+\n+ @ensure_deferred\n+ async def test_issue_comment_by_ursabot(self):\n+ payload = self.load_fixture('issue-comment-by-ursabot')\n+ await self.trigger('issue_comment', payload=payload)\n+ assert len(self.hook.master.data.updates.changesAdded) == 0\n+\n+ @ensure_deferred\n+ async def test_issue_comment_with_empty_command(self):\n+ # responds to the comment\n+ request_json = {'body': 'Unknown command \"\"'}\n+ response_json = ''\n+ self.http.expect('post', '/repos/ursa-labs/ursabot/issues/26/comments',\n+ json=request_json, content_json=response_json)\n+\n+ payload = self.load_fixture('issue-comment-with-empty-command')\n+ await self.trigger('issue_comment', payload=payload)\n+ assert len(self.hook.master.data.updates.changesAdded) == 0\n+\n+ @ensure_deferred\n+ async def test_issue_comment_without_pull_request(self):\n+ # responds to the comment\n+ request_json = {\n+ 'body': 'Ursabot only listens to pull request comments!'\n+ }\n+ response_json = ''\n+ self.http.expect('post', '/repos/ursa-labs/ursabot/issues/19/comments',\n+ json=request_json, content_json=response_json)\n+\n+ payload = self.load_fixture('issue-comment-without-pull-request')\n+ await self.trigger('issue_comment', payload=payload)\n+ assert len(self.hook.master.data.updates.changesAdded) == 0\n+\n+ @ensure_deferred\n+ async def test_issue_comment_build_command(self):\n+ # handle_issue_comment queries the pull request\n+ request_json = self.load_fixture('pull-request-26')\n+ self.http.expect('get', '/repos/ursa-labs/ursabot/pulls/26',\n+ content_json=request_json)\n+ # tigger handle_pull_request which fetches the commit\n+ request_json = self.load_fixture('pull-request-26-commit')\n+ commit = '2705da2b616b98fa6010a25813c5a7a27456f71d'\n+ self.http.expect('get', f'/repos/ursa-labs/ursabot/commits/{commit}',\n+ content_json=request_json)\n+\n+ # then responds to the comment\n+ request_json = {'body': \"I've successfully started builds for this PR\"}\n+ response_json = ''\n+ self.http.expect('post', '/repos/ursa-labs/ursabot/issues/26/comments',\n+ json=request_json, content_json=response_json)\n+\n+ payload = self.load_fixture('issue-comment-build-command')\n+ await self.trigger('issue_comment', payload=payload)\n+ assert len(self.hook.master.data.updates.changesAdded) == 1" - }, - { - "sha": "3ff0e88660cf186420e8bc672735e4d446963192", - "filename": "ursabot/utils.py", - "status": "added", - "additions": 10, - "deletions": 0, - "changes": 10, - "blob_url": "https://github.com/ursa-labs/ursabot/blob/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/utils.py", - "raw_url": "https://github.com/ursa-labs/ursabot/raw/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/utils.py", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/utils.py?ref=70267dee34884e4b972388e1b30d57f6248c58d0", - "patch": "@@ -0,0 +1,10 @@\n+import functools\n+from twisted.internet import defer\n+\n+\n+def ensure_deferred(f):\n+ @functools.wraps(f)\n+ def wrapper(*args, **kwargs):\n+ result = f(*args, **kwargs)\n+ return defer.ensureDeferred(result)\n+ return wrapper" - } -] \ No newline at end of file diff --git a/dev/archery/archery/tests/fixtures/pull-request-26.json b/dev/archery/archery/tests/fixtures/pull-request-26.json deleted file mode 100644 index d295afb396e3..000000000000 --- a/dev/archery/archery/tests/fixtures/pull-request-26.json +++ /dev/null @@ -1,329 +0,0 @@ -{ - "url": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26", - "id": 267785552, - "node_id": "MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy", - "html_url": "https://github.com/ursa-labs/ursabot/pull/26", - "diff_url": "https://github.com/ursa-labs/ursabot/pull/26.diff", - "patch_url": "https://github.com/ursa-labs/ursabot/pull/26.patch", - "issue_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26", - "number": 26, - "state": "open", - "locked": false, - "title": "Unittests for GithubHook", - "user": { - "login": "kszucs", - "id": 961747, - "node_id": "MDQ6VXNlcjk2MTc0Nw==", - "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/kszucs", - "html_url": "https://github.com/kszucs", - "followers_url": "https://api.github.com/users/kszucs/followers", - "following_url": "https://api.github.com/users/kszucs/following{/other_user}", - "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}", - "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions", - "organizations_url": "https://api.github.com/users/kszucs/orgs", - "repos_url": "https://api.github.com/users/kszucs/repos", - "events_url": "https://api.github.com/users/kszucs/events{/privacy}", - "received_events_url": "https://api.github.com/users/kszucs/received_events", - "type": "User", - "site_admin": false - }, - "body": "", - "body_html": "", - "body_text": "", - "created_at": "2019-04-05T11:22:15Z", - "updated_at": "2019-04-05T12:01:40Z", - "closed_at": null, - "merged_at": null, - "merge_commit_sha": "cc5dc3606988b3824be54df779ed2028776113cb", - "assignee": null, - "assignees": [], - "requested_reviewers": [], - "requested_teams": [], - "labels": [], - "milestone": null, - "commits_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26/commits", - "review_comments_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26/comments", - "review_comment_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls/comments{/number}", - "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments", - "statuses_url": "https://api.github.com/repos/ursa-labs/ursabot/statuses/2705da2b616b98fa6010a25813c5a7a27456f71d", - "head": { - "label": "ursa-labs:test-hook", - "ref": "test-hook", - "sha": "2705da2b616b98fa6010a25813c5a7a27456f71d", - "user": { - "login": "ursa-labs", - "id": 46514972, - "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy", - "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/ursa-labs", - "html_url": "https://github.com/ursa-labs", - "followers_url": "https://api.github.com/users/ursa-labs/followers", - "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}", - "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}", - "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions", - "organizations_url": "https://api.github.com/users/ursa-labs/orgs", - "repos_url": "https://api.github.com/users/ursa-labs/repos", - "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}", - "received_events_url": "https://api.github.com/users/ursa-labs/received_events", - "type": "Organization", - "site_admin": false - }, - "repo": { - "id": 169101701, - "node_id": "MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=", - "name": "ursabot", - "full_name": "ursa-labs/ursabot", - "private": false, - "owner": { - "login": "ursa-labs", - "id": 46514972, - "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy", - "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/ursa-labs", - "html_url": "https://github.com/ursa-labs", - "followers_url": "https://api.github.com/users/ursa-labs/followers", - "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}", - "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}", - "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions", - "organizations_url": "https://api.github.com/users/ursa-labs/orgs", - "repos_url": "https://api.github.com/users/ursa-labs/repos", - "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}", - "received_events_url": "https://api.github.com/users/ursa-labs/received_events", - "type": "Organization", - "site_admin": false - }, - "html_url": "https://github.com/ursa-labs/ursabot", - "description": null, - "fork": false, - "url": "https://api.github.com/repos/ursa-labs/ursabot", - "forks_url": "https://api.github.com/repos/ursa-labs/ursabot/forks", - "keys_url": "https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}", - "collaborators_url": "https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}", - "teams_url": "https://api.github.com/repos/ursa-labs/ursabot/teams", - "hooks_url": "https://api.github.com/repos/ursa-labs/ursabot/hooks", - "issue_events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}", - "events_url": "https://api.github.com/repos/ursa-labs/ursabot/events", - "assignees_url": "https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}", - "branches_url": "https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}", - "tags_url": "https://api.github.com/repos/ursa-labs/ursabot/tags", - "blobs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}", - "git_tags_url": "https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}", - "git_refs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}", - "trees_url": "https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}", - "statuses_url": "https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}", - "languages_url": "https://api.github.com/repos/ursa-labs/ursabot/languages", - "stargazers_url": "https://api.github.com/repos/ursa-labs/ursabot/stargazers", - "contributors_url": "https://api.github.com/repos/ursa-labs/ursabot/contributors", - "subscribers_url": "https://api.github.com/repos/ursa-labs/ursabot/subscribers", - "subscription_url": "https://api.github.com/repos/ursa-labs/ursabot/subscription", - "commits_url": "https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}", - "git_commits_url": "https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}", - "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/comments{/number}", - "issue_comment_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}", - "compare_url": "https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}", - "merges_url": "https://api.github.com/repos/ursa-labs/ursabot/merges", - "archive_url": "https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}", - "downloads_url": "https://api.github.com/repos/ursa-labs/ursabot/downloads", - "issues_url": "https://api.github.com/repos/ursa-labs/ursabot/issues{/number}", - "pulls_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}", - "milestones_url": "https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}", - "notifications_url": "https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}", - "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/labels{/name}", - "releases_url": "https://api.github.com/repos/ursa-labs/ursabot/releases{/id}", - "deployments_url": "https://api.github.com/repos/ursa-labs/ursabot/deployments", - "created_at": "2019-02-04T15:40:31Z", - "updated_at": "2019-04-04T17:49:10Z", - "pushed_at": "2019-04-05T12:01:40Z", - "git_url": "git://github.com/ursa-labs/ursabot.git", - "ssh_url": "git@github.com:ursa-labs/ursabot.git", - "clone_url": "https://github.com/ursa-labs/ursabot.git", - "svn_url": "https://github.com/ursa-labs/ursabot", - "homepage": null, - "size": 898, - "stargazers_count": 1, - "watchers_count": 1, - "language": "Jupyter Notebook", - "has_issues": true, - "has_projects": true, - "has_downloads": true, - "has_wiki": true, - "has_pages": false, - "forks_count": 0, - "mirror_url": null, - "archived": false, - "disabled": false, - "open_issues_count": 19, - "license": null, - "forks": 0, - "open_issues": 19, - "watchers": 1, - "default_branch": "master" - } - }, - "base": { - "label": "ursa-labs:master", - "ref": "master", - "sha": "a162ad254b589b924db47e057791191b39613fd5", - "user": { - "login": "ursa-labs", - "id": 46514972, - "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy", - "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/ursa-labs", - "html_url": "https://github.com/ursa-labs", - "followers_url": "https://api.github.com/users/ursa-labs/followers", - "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}", - "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}", - "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions", - "organizations_url": "https://api.github.com/users/ursa-labs/orgs", - "repos_url": "https://api.github.com/users/ursa-labs/repos", - "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}", - "received_events_url": "https://api.github.com/users/ursa-labs/received_events", - "type": "Organization", - "site_admin": false - }, - "repo": { - "id": 169101701, - "node_id": "MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=", - "name": "ursabot", - "full_name": "ursa-labs/ursabot", - "private": false, - "owner": { - "login": "ursa-labs", - "id": 46514972, - "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy", - "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/ursa-labs", - "html_url": "https://github.com/ursa-labs", - "followers_url": "https://api.github.com/users/ursa-labs/followers", - "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}", - "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}", - "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions", - "organizations_url": "https://api.github.com/users/ursa-labs/orgs", - "repos_url": "https://api.github.com/users/ursa-labs/repos", - "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}", - "received_events_url": "https://api.github.com/users/ursa-labs/received_events", - "type": "Organization", - "site_admin": false - }, - "html_url": "https://github.com/ursa-labs/ursabot", - "description": null, - "fork": false, - "url": "https://api.github.com/repos/ursa-labs/ursabot", - "forks_url": "https://api.github.com/repos/ursa-labs/ursabot/forks", - "keys_url": "https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}", - "collaborators_url": "https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}", - "teams_url": "https://api.github.com/repos/ursa-labs/ursabot/teams", - "hooks_url": "https://api.github.com/repos/ursa-labs/ursabot/hooks", - "issue_events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}", - "events_url": "https://api.github.com/repos/ursa-labs/ursabot/events", - "assignees_url": "https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}", - "branches_url": "https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}", - "tags_url": "https://api.github.com/repos/ursa-labs/ursabot/tags", - "blobs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}", - "git_tags_url": "https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}", - "git_refs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}", - "trees_url": "https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}", - "statuses_url": "https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}", - "languages_url": "https://api.github.com/repos/ursa-labs/ursabot/languages", - "stargazers_url": "https://api.github.com/repos/ursa-labs/ursabot/stargazers", - "contributors_url": "https://api.github.com/repos/ursa-labs/ursabot/contributors", - "subscribers_url": "https://api.github.com/repos/ursa-labs/ursabot/subscribers", - "subscription_url": "https://api.github.com/repos/ursa-labs/ursabot/subscription", - "commits_url": "https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}", - "git_commits_url": "https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}", - "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/comments{/number}", - "issue_comment_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}", - "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}", - "compare_url": "https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}", - "merges_url": "https://api.github.com/repos/ursa-labs/ursabot/merges", - "archive_url": "https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}", - "downloads_url": "https://api.github.com/repos/ursa-labs/ursabot/downloads", - "issues_url": "https://api.github.com/repos/ursa-labs/ursabot/issues{/number}", - "pulls_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}", - "milestones_url": "https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}", - "notifications_url": "https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}", - "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/labels{/name}", - "releases_url": "https://api.github.com/repos/ursa-labs/ursabot/releases{/id}", - "deployments_url": "https://api.github.com/repos/ursa-labs/ursabot/deployments", - "created_at": "2019-02-04T15:40:31Z", - "updated_at": "2019-04-04T17:49:10Z", - "pushed_at": "2019-04-05T12:01:40Z", - "git_url": "git://github.com/ursa-labs/ursabot.git", - "ssh_url": "git@github.com:ursa-labs/ursabot.git", - "clone_url": "https://github.com/ursa-labs/ursabot.git", - "svn_url": "https://github.com/ursa-labs/ursabot", - "homepage": null, - "size": 898, - "stargazers_count": 1, - "watchers_count": 1, - "language": "Jupyter Notebook", - "has_issues": true, - "has_projects": true, - "has_downloads": true, - "has_wiki": true, - "has_pages": false, - "forks_count": 0, - "mirror_url": null, - "archived": false, - "disabled": false, - "open_issues_count": 19, - "license": null, - "forks": 0, - "open_issues": 19, - "watchers": 1, - "default_branch": "master" - } - }, - "_links": { - "self": { - "href": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26" - }, - "html": { - "href": "https://github.com/ursa-labs/ursabot/pull/26" - }, - "issue": { - "href": "https://api.github.com/repos/ursa-labs/ursabot/issues/26" - }, - "comments": { - "href": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments" - }, - "review_comments": { - "href": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26/comments" - }, - "review_comment": { - "href": "https://api.github.com/repos/ursa-labs/ursabot/pulls/comments{/number}" - }, - "commits": { - "href": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26/commits" - }, - "statuses": { - "href": "https://api.github.com/repos/ursa-labs/ursabot/statuses/2705da2b616b98fa6010a25813c5a7a27456f71d" - } - }, - "author_association": "MEMBER", - "merged": false, - "mergeable": true, - "rebaseable": true, - "mergeable_state": "unstable", - "merged_by": null, - "comments": 5, - "review_comments": 0, - "maintainer_can_modify": false, - "commits": 2, - "additions": 1124, - "deletions": 0, - "changed_files": 7 -} \ No newline at end of file diff --git a/dev/archery/archery/tests/test_benchmarks.py b/dev/archery/archery/tests/test_benchmarks.py deleted file mode 100644 index fab1e8d44321..000000000000 --- a/dev/archery/archery/tests/test_benchmarks.py +++ /dev/null @@ -1,383 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -import json - -from archery.benchmark.codec import JsonEncoder -from archery.benchmark.core import Benchmark, median -from archery.benchmark.compare import ( - BenchmarkComparator, RunnerComparator -) -from archery.benchmark.google import ( - GoogleBenchmark, GoogleBenchmarkObservation -) -from archery.benchmark.runner import StaticBenchmarkRunner - - -def test_benchmark_comparator(): - unit = "micros" - - assert not BenchmarkComparator( - Benchmark("contender", unit, True, [10], unit, [1]), - Benchmark("baseline", unit, True, [20], unit, [1]), - ).regression - - assert BenchmarkComparator( - Benchmark("contender", unit, False, [10], unit, [1]), - Benchmark("baseline", unit, False, [20], unit, [1]), - ).regression - - assert BenchmarkComparator( - Benchmark("contender", unit, True, [20], unit, [1]), - Benchmark("baseline", unit, True, [10], unit, [1]), - ).regression - - assert not BenchmarkComparator( - Benchmark("contender", unit, False, [20], unit, [1]), - Benchmark("baseline", unit, False, [10], unit, [1]), - ).regression - - -def test_static_runner_from_json_not_a_regression(): - archery_result = { - "suites": [ - { - "name": "arrow-value-parsing-benchmark", - "benchmarks": [ - { - "name": "FloatParsing", - "unit": "items_per_second", - "less_is_better": False, - "values": [ - 109941112.87296811 - ], - "time_unit": "ns", - "times": [ - 9095.800104330105 - ] - }, - ] - } - ] - } - - contender = StaticBenchmarkRunner.from_json(json.dumps(archery_result)) - baseline = StaticBenchmarkRunner.from_json(json.dumps(archery_result)) - [comparison] = RunnerComparator(contender, baseline).comparisons - assert not comparison.regression - - -def test_static_runner_from_json_regression(): - archery_result = { - "suites": [ - { - "name": "arrow-value-parsing-benchmark", - "benchmarks": [ - { - "name": "FloatParsing", - "unit": "items_per_second", - "less_is_better": False, - "values": [ - 109941112.87296811 - ], - "time_unit": "ns", - "times": [ - 9095.800104330105 - ] - }, - ] - } - ] - } - - contender = StaticBenchmarkRunner.from_json(json.dumps(archery_result)) - - # introduce artificial regression - archery_result['suites'][0]['benchmarks'][0]['values'][0] *= 2 - baseline = StaticBenchmarkRunner.from_json(json.dumps(archery_result)) - - [comparison] = RunnerComparator(contender, baseline).comparisons - assert comparison.regression - - -def test_benchmark_median(): - assert median([10]) == 10 - assert median([1, 2, 3]) == 2 - assert median([1, 2]) == 1.5 - assert median([1, 2, 3, 4]) == 2.5 - assert median([1, 1, 1, 1]) == 1 - try: - median([]) - assert False - except ValueError: - pass - - -def assert_benchmark(name, google_result, archery_result): - observation = GoogleBenchmarkObservation(**google_result) - benchmark = GoogleBenchmark(name, [observation]) - result = json.dumps(benchmark, cls=JsonEncoder) - assert json.loads(result) == archery_result - - -def test_items_per_second(): - name = "ArrayArrayKernel/32768/0" - google_result = { - "cpu_time": 116292.58886653671, - "items_per_second": 281772039.9844759, - "iterations": 5964, - "name": name, - "null_percent": 0.0, - "real_time": 119811.77313729875, - "repetition_index": 0, - "repetitions": 0, - "run_name": name, - "run_type": "iteration", - "size": 32768.0, - "threads": 1, - "time_unit": "ns", - } - archery_result = { - "counters": {"iterations": 5964, - "null_percent": 0.0, - "repetition_index": 0, - "repetitions": 0, - "run_name": name, - "threads": 1}, - "name": name, - "unit": "items_per_second", - "less_is_better": False, - "values": [281772039.9844759], - "time_unit": "ns", - "times": [119811.77313729875], - } - assert "items_per_second" in google_result - assert "bytes_per_second" not in google_result - assert_benchmark(name, google_result, archery_result) - - -def test_bytes_per_second(): - name = "BufferOutputStreamLargeWrites/real_time" - google_result = { - "bytes_per_second": 1890209037.3405428, - "cpu_time": 17018127.659574457, - "iterations": 47, - "name": name, - "real_time": 17458386.53190963, - "repetition_index": 1, - "repetitions": 0, - "run_name": name, - "run_type": "iteration", - "threads": 1, - "time_unit": "ns", - } - archery_result = { - "counters": {"iterations": 47, - "repetition_index": 1, - "repetitions": 0, - "run_name": name, - "threads": 1}, - "name": name, - "unit": "bytes_per_second", - "less_is_better": False, - "values": [1890209037.3405428], - "time_unit": "ns", - "times": [17458386.53190963], - } - assert "items_per_second" not in google_result - assert "bytes_per_second" in google_result - assert_benchmark(name, google_result, archery_result) - - -def test_both_items_and_bytes_per_second(): - name = "ArrayArrayKernel/32768/0" - google_result = { - "bytes_per_second": 281772039.9844759, - "cpu_time": 116292.58886653671, - "items_per_second": 281772039.9844759, - "iterations": 5964, - "name": name, - "null_percent": 0.0, - "real_time": 119811.77313729875, - "repetition_index": 0, - "repetitions": 0, - "run_name": name, - "run_type": "iteration", - "size": 32768.0, - "threads": 1, - "time_unit": "ns", - } - # Note that bytes_per_second trumps items_per_second - archery_result = { - "counters": {"iterations": 5964, - "null_percent": 0.0, - "repetition_index": 0, - "repetitions": 0, - "run_name": name, - "threads": 1}, - "name": name, - "unit": "bytes_per_second", - "less_is_better": False, - "values": [281772039.9844759], - "time_unit": "ns", - "times": [119811.77313729875], - } - assert "items_per_second" in google_result - assert "bytes_per_second" in google_result - assert_benchmark(name, google_result, archery_result) - - -def test_neither_items_nor_bytes_per_second(): - name = "AllocateDeallocate/size:1048576/real_time" - google_result = { - "cpu_time": 1778.6004847419827, - "iterations": 352765, - "name": name, - "real_time": 1835.3137357788837, - "repetition_index": 0, - "repetitions": 0, - "run_name": name, - "run_type": "iteration", - "threads": 1, - "time_unit": "ns", - } - archery_result = { - "counters": {"iterations": 352765, - "repetition_index": 0, - "repetitions": 0, - "run_name": name, - "threads": 1}, - "name": name, - "unit": "ns", - "less_is_better": True, - "values": [1835.3137357788837], - "time_unit": "ns", - "times": [1835.3137357788837], - } - assert "items_per_second" not in google_result - assert "bytes_per_second" not in google_result - assert_benchmark(name, google_result, archery_result) - - -def test_prefer_real_time(): - name = "AllocateDeallocate/size:1048576/real_time" - google_result = { - "cpu_time": 1778.6004847419827, - "iterations": 352765, - "name": name, - "real_time": 1835.3137357788837, - "repetition_index": 0, - "repetitions": 0, - "run_name": name, - "run_type": "iteration", - "threads": 1, - "time_unit": "ns", - } - archery_result = { - "counters": {"iterations": 352765, - "repetition_index": 0, - "repetitions": 0, - "run_name": name, - "threads": 1}, - "name": name, - "unit": "ns", - "less_is_better": True, - "values": [1835.3137357788837], - "time_unit": "ns", - "times": [1835.3137357788837], - } - assert name.endswith("/real_time") - assert_benchmark(name, google_result, archery_result) - - -def test_prefer_cpu_time(): - name = "AllocateDeallocate/size:1048576" - google_result = { - "cpu_time": 1778.6004847419827, - "iterations": 352765, - "name": name, - "real_time": 1835.3137357788837, - "repetition_index": 0, - "repetitions": 0, - "run_name": name, - "run_type": "iteration", - "threads": 1, - "time_unit": "ns", - } - archery_result = { - "counters": {"iterations": 352765, - "repetition_index": 0, - "repetitions": 0, - "run_name": name, - "threads": 1}, - "name": name, - "unit": "ns", - "less_is_better": True, - "values": [1778.6004847419827], - "time_unit": "ns", - "times": [1835.3137357788837], - } - assert not name.endswith("/real_time") - assert_benchmark(name, google_result, archery_result) - - -def test_omits_aggregates(): - name = "AllocateDeallocate/size:1048576/real_time" - google_aggregate = { - "aggregate_name": "mean", - "cpu_time": 1757.428694267678, - "iterations": 3, - "name": "AllocateDeallocate/size:1048576/real_time_mean", - "real_time": 1849.3869337041162, - "repetitions": 0, - "run_name": name, - "run_type": "aggregate", - "threads": 1, - "time_unit": "ns", - } - google_result = { - "cpu_time": 1778.6004847419827, - "iterations": 352765, - "name": name, - "real_time": 1835.3137357788837, - "repetition_index": 0, - "repetitions": 0, - "run_name": name, - "run_type": "iteration", - "threads": 1, - "time_unit": "ns", - } - archery_result = { - "counters": {"iterations": 352765, - "repetition_index": 0, - "repetitions": 0, - "run_name": name, - "threads": 1}, - "name": name, - "unit": "ns", - "less_is_better": True, - "values": [1835.3137357788837], - "time_unit": "ns", - "times": [1835.3137357788837], - } - assert google_aggregate["run_type"] == "aggregate" - assert google_result["run_type"] == "iteration" - observation1 = GoogleBenchmarkObservation(**google_aggregate) - observation2 = GoogleBenchmarkObservation(**google_result) - benchmark = GoogleBenchmark(name, [observation1, observation2]) - result = json.dumps(benchmark, cls=JsonEncoder) - assert json.loads(result) == archery_result diff --git a/dev/archery/archery/tests/test_bot.py b/dev/archery/archery/tests/test_bot.py deleted file mode 100644 index e00853ceb2cb..000000000000 --- a/dev/archery/archery/tests/test_bot.py +++ /dev/null @@ -1,201 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -import json -from unittest.mock import Mock - -import click -import pytest -import responses as rsps - -from archery.bot import CommentBot, CommandError, group - - -@pytest.fixture -def responses(): - with rsps.RequestsMock() as mock: - yield mock - - -def github_url(path): - return 'https://api.github.com:443/{}'.format(path.strip('/')) - - -@group() -def custom_handler(): - pass - - -@custom_handler.command() -@click.pass_obj -def extra(obj): - return obj - - -@custom_handler.command() -@click.option('--force', '-f', is_flag=True) -def build(force): - return force - - -@custom_handler.command() -@click.option('--name', required=True) -def benchmark(name): - return name - - -def test_click_based_commands(): - assert custom_handler('build') is False - assert custom_handler('build -f') is True - - assert custom_handler('benchmark --name strings') == 'strings' - with pytest.raises(CommandError): - assert custom_handler('benchmark') - - assert custom_handler('extra', extra='data') == {'extra': 'data'} - - -@pytest.mark.parametrize('fixture_name', [ - # the bot is not mentioned, nothing to do - 'event-issue-comment-not-mentioning-ursabot.json', - # don't respond to itself, it prevents recursive comment storms! - 'event-issue-comment-by-ursabot.json', - # non-authorized user sent the comment, do not respond - 'event-issue-comment-by-non-authorized-user.json', -]) -def test_noop_events(load_fixture, fixture_name): - payload = load_fixture(fixture_name) - - handler = Mock() - bot = CommentBot(name='ursabot', token='', handler=handler) - bot.handle('issue_comment', payload) - - handler.assert_not_called() - - -def test_issue_comment_without_pull_request(load_fixture, responses): - responses.add( - responses.GET, - github_url('/repositories/169101701/issues/19'), - json=load_fixture('issue-19.json'), - status=200 - ) - responses.add( - responses.GET, - github_url('repos/ursa-labs/ursabot/pulls/19'), - json={}, - status=404 - ) - responses.add( - responses.POST, - github_url('/repos/ursa-labs/ursabot/issues/19/comments'), - json={} - ) - - def handler(command, **kwargs): - pass - - payload = load_fixture('event-issue-comment-without-pull-request.json') - bot = CommentBot(name='ursabot', token='', handler=handler) - bot.handle('issue_comment', payload) - - post = responses.calls[2] - assert json.loads(post.request.body) == { - 'body': "The comment bot only listens to pull request comments!" - } - - -def test_respond_with_usage(load_fixture, responses): - responses.add( - responses.GET, - github_url('/repositories/169101701/issues/26'), - json=load_fixture('issue-26.json'), - status=200 - ) - responses.add( - responses.GET, - github_url('/repos/ursa-labs/ursabot/pulls/26'), - json=load_fixture('pull-request-26.json'), - status=200 - ) - responses.add( - responses.GET, - github_url('/repos/ursa-labs/ursabot/issues/comments/480243811'), - json=load_fixture('issue-comment-480243811.json') - ) - responses.add( - responses.POST, - github_url('/repos/ursa-labs/ursabot/issues/26/comments'), - json={} - ) - - def handler(command, **kwargs): - raise CommandError('test-usage') - - payload = load_fixture('event-issue-comment-with-empty-command.json') - bot = CommentBot(name='ursabot', token='', handler=handler) - bot.handle('issue_comment', payload) - - post = responses.calls[3] - assert json.loads(post.request.body) == {'body': '```\ntest-usage\n```'} - - -@pytest.mark.parametrize(('command', 'reaction'), [ - ('@ursabot build', '+1'), - ('@ursabot listen', '-1'), -]) -def test_issue_comment_with_commands(load_fixture, responses, command, - reaction): - responses.add( - responses.GET, - github_url('/repositories/169101701/issues/26'), - json=load_fixture('issue-26.json'), - status=200 - ) - responses.add( - responses.GET, - github_url('/repos/ursa-labs/ursabot/pulls/26'), - json=load_fixture('pull-request-26.json'), - status=200 - ) - responses.add( - responses.GET, - github_url('/repos/ursa-labs/ursabot/issues/comments/480248726'), - json=load_fixture('issue-comment-480248726.json') - ) - responses.add( - responses.POST, - github_url( - '/repos/ursa-labs/ursabot/issues/comments/480248726/reactions' - ), - json={} - ) - - def handler(command, **kwargs): - if command == 'build': - return True - else: - raise ValueError('Only `build` command is supported.') - - payload = load_fixture('event-issue-comment-build-command.json') - payload["comment"]["body"] = command - - bot = CommentBot(name='ursabot', token='', handler=handler) - bot.handle('issue_comment', payload) - - post = responses.calls[3] - assert json.loads(post.request.body) == {'content': reaction} diff --git a/dev/archery/archery/tests/test_cli.py b/dev/archery/archery/tests/test_cli.py deleted file mode 100644 index b3199dfaf1fb..000000000000 --- a/dev/archery/archery/tests/test_cli.py +++ /dev/null @@ -1,162 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -from unittest.mock import patch - -from click.testing import CliRunner - -from archery.cli import archery -from archery.docker import DockerCompose - - -@patch.object(DockerCompose, "pull") -@patch.object(DockerCompose, "build") -@patch.object(DockerCompose, "run") -def test_docker_run_with_custom_command(run, build, pull): - # with custom command - args = ["docker", "run", "ubuntu-cpp", "bash"] - result = CliRunner().invoke(archery, args) - assert result.exit_code == 0 - pull.assert_called_once_with( - "ubuntu-cpp", pull_leaf=True, using_docker=False - ) - build.assert_called_once_with( - "ubuntu-cpp", - use_cache=True, - use_leaf_cache=True, - using_docker=False, - using_buildx=False - ) - run.assert_called_once_with( - "ubuntu-cpp", - command="bash", - env={}, - user=None, - using_docker=False, - volumes=(), - ) - - -@patch.object(DockerCompose, "pull") -@patch.object(DockerCompose, "build") -@patch.object(DockerCompose, "run") -def test_docker_run_options(run, build, pull): - # environment variables and volumes - args = [ - "docker", - "run", - "-e", - "ARROW_GANDIVA=OFF", - "-e", - "ARROW_FLIGHT=ON", - "--volume", - "./build:/build", - "-v", - "./ccache:/ccache:delegated", - "-u", - "root", - "ubuntu-cpp", - ] - result = CliRunner().invoke(archery, args) - assert result.exit_code == 0 - pull.assert_called_once_with( - "ubuntu-cpp", pull_leaf=True, using_docker=False - ) - build.assert_called_once_with( - "ubuntu-cpp", - use_cache=True, - use_leaf_cache=True, - using_docker=False, - using_buildx=False - ) - run.assert_called_once_with( - "ubuntu-cpp", - command=None, - env={"ARROW_GANDIVA": "OFF", "ARROW_FLIGHT": "ON"}, - user="root", - using_docker=False, - volumes=( - "./build:/build", - "./ccache:/ccache:delegated", - ), - ) - - -@patch.object(DockerCompose, "run") -def test_docker_run_without_pulling_or_building(run): - args = ["docker", "run", "--no-pull", "--no-build", "ubuntu-cpp"] - result = CliRunner().invoke(archery, args) - assert result.exit_code == 0 - run.assert_called_once_with( - "ubuntu-cpp", - command=None, - env={}, - user=None, - using_docker=False, - volumes=(), - ) - - -@patch.object(DockerCompose, "pull") -@patch.object(DockerCompose, "build") -def test_docker_run_only_pulling_and_building(build, pull): - args = ["docker", "run", "ubuntu-cpp", "--build-only"] - result = CliRunner().invoke(archery, args) - assert result.exit_code == 0 - pull.assert_called_once_with( - "ubuntu-cpp", pull_leaf=True, using_docker=False - ) - build.assert_called_once_with( - "ubuntu-cpp", - use_cache=True, - use_leaf_cache=True, - using_docker=False, - using_buildx=False - ) - - -@patch.object(DockerCompose, "build") -@patch.object(DockerCompose, "run") -def test_docker_run_without_build_cache(run, build): - args = [ - "docker", - "run", - "--no-pull", - "--force-build", - "--user", - "me", - "--no-cache", - "--no-leaf-cache", - "ubuntu-cpp", - ] - result = CliRunner().invoke(archery, args) - assert result.exit_code == 0 - build.assert_called_once_with( - "ubuntu-cpp", - use_cache=False, - use_leaf_cache=False, - using_docker=False, - using_buildx=False - ) - run.assert_called_once_with( - "ubuntu-cpp", - command=None, - env={}, - user="me", - using_docker=False, - volumes=(), - ) diff --git a/dev/archery/archery/tests/test_docker.py b/dev/archery/archery/tests/test_docker.py deleted file mode 100644 index 09dcd27a7133..000000000000 --- a/dev/archery/archery/tests/test_docker.py +++ /dev/null @@ -1,512 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -import collections -import os -import re -import subprocess -from unittest import mock - -import pytest - -from archery.docker import DockerCompose -from archery.testing import assert_subprocess_calls, override_env, PartialEnv - - -missing_service_compose_yml = """ -version: '3.5' - -x-hierarchy: - - foo: - - sub-foo: - - sub-sub-foo - - another-sub-sub-foo - - bar: - - sub-bar - - baz - -services: - foo: - image: org/foo - sub-sub-foo: - image: org/sub-sub-foo - another-sub-sub-foo: - image: org/another-sub-sub-foo - bar: - image: org/bar - sub-bar: - image: org/sub-bar - baz: - image: org/baz -""" - -missing_node_compose_yml = """ -version: '3.5' - -x-hierarchy: - - foo: - - sub-foo: - - sub-sub-foo - - another-sub-sub-foo - - bar - - baz - -services: - foo: - image: org/foo - sub-foo: - image: org/sub-foo - sub-sub-foo: - image: org/sub-foo-foo - another-sub-sub-foo: - image: org/another-sub-sub-foo - bar: - image: org/bar - sub-bar: - image: org/sub-bar - baz: - image: org/baz -""" - -ok_compose_yml = """ -version: '3.5' - -x-hierarchy: - - foo: - - sub-foo: - - sub-sub-foo - - another-sub-sub-foo - - bar: - - sub-bar - - baz - -services: - foo: - image: org/foo - sub-foo: - image: org/sub-foo - sub-sub-foo: - image: org/sub-sub-foo - another-sub-sub-foo: - image: org/another-sub-sub-foo - bar: - image: org/bar - sub-bar: - image: org/sub-bar - baz: - image: org/baz -""" - -arrow_compose_yml = """ -version: '3.5' - -x-with-gpus: - - ubuntu-cuda - -x-hierarchy: - - conda-cpp: - - conda-python: - - conda-python-pandas - - conda-python-dask - - ubuntu-cpp: - - ubuntu-cpp-cmake32 - - ubuntu-c-glib: - - ubuntu-ruby - - ubuntu-cuda - -services: - conda-cpp: - image: org/conda-cpp - build: - context: . - dockerfile: ci/docker/conda-cpp.dockerfile - conda-python: - image: org/conda-python - build: - context: . - dockerfile: ci/docker/conda-cpp.dockerfile - args: - python: 3.6 - conda-python-pandas: - image: org/conda-python-pandas - build: - context: . - dockerfile: ci/docker/conda-python-pandas.dockerfile - conda-python-dask: - image: org/conda-python-dask - ubuntu-cpp: - image: org/ubuntu-cpp - build: - context: . - dockerfile: ci/docker/ubuntu-${UBUNTU}-cpp.dockerfile - ubuntu-cpp-cmake32: - image: org/ubuntu-cpp-cmake32 - ubuntu-c-glib: - image: org/ubuntu-c-glib - ubuntu-ruby: - image: org/ubuntu-ruby - ubuntu-cuda: - image: org/ubuntu-cuda - environment: - CUDA_ENV: 1 - OTHER_ENV: 2 - volumes: - - /host:/container - command: /bin/bash -c "echo 1 > /tmp/dummy && cat /tmp/dummy" -""" - -arrow_compose_env = { - 'UBUNTU': '20.04', # overridden below - 'PYTHON': '3.6', - 'PANDAS': 'latest', - 'DASK': 'latest', # overridden below -} - - -def create_config(directory, yml_content, env_content=None): - env_path = directory / '.env' - config_path = directory / 'docker-compose.yml' - - with config_path.open('w') as fp: - fp.write(yml_content) - - if env_content is not None: - with env_path.open('w') as fp: - for k, v in env_content.items(): - fp.write("{}={}\n".format(k, v)) - - return config_path - - -def format_run(args): - cmd = ["run", "--rm"] - if isinstance(args, str): - return " ".join(cmd + [args]) - else: - return cmd + args - - -@pytest.fixture -def arrow_compose_path(tmpdir): - return create_config(tmpdir, arrow_compose_yml, arrow_compose_env) - - -def test_config_validation(tmpdir): - config_path = create_config(tmpdir, missing_service_compose_yml) - msg = "`sub-foo` is defined in `x-hierarchy` bot not in `services`" - with pytest.raises(ValueError, match=msg): - DockerCompose(config_path) - - config_path = create_config(tmpdir, missing_node_compose_yml) - msg = "`sub-bar` is defined in `services` but not in `x-hierarchy`" - with pytest.raises(ValueError, match=msg): - DockerCompose(config_path) - - config_path = create_config(tmpdir, ok_compose_yml) - DockerCompose(config_path) # no issue - - -def assert_docker_calls(compose, expected_args): - base_command = ['docker'] - expected_commands = [] - for args in expected_args: - if isinstance(args, str): - args = re.split(r"\s", args) - expected_commands.append(base_command + args) - return assert_subprocess_calls(expected_commands, check=True) - - -def assert_compose_calls(compose, expected_args, env=mock.ANY): - base_command = ['docker-compose', '--file', str(compose.config.path)] - expected_commands = [] - for args in expected_args: - if isinstance(args, str): - args = re.split(r"\s", args) - expected_commands.append(base_command + args) - return assert_subprocess_calls(expected_commands, check=True, env=env) - - -def test_arrow_example_validation_passes(arrow_compose_path): - DockerCompose(arrow_compose_path) - - -def test_compose_default_params_and_env(arrow_compose_path): - compose = DockerCompose(arrow_compose_path, params=dict( - UBUNTU='18.04', - DASK='master' - )) - assert compose.config.dotenv == arrow_compose_env - assert compose.config.params == { - 'UBUNTU': '18.04', - 'DASK': 'master', - } - - -def test_forwarding_env_variables(arrow_compose_path): - expected_calls = [ - "pull --ignore-pull-failures conda-cpp", - "build conda-cpp", - ] - expected_env = PartialEnv( - MY_CUSTOM_VAR_A='a', - MY_CUSTOM_VAR_B='b' - ) - with override_env({'MY_CUSTOM_VAR_A': 'a', 'MY_CUSTOM_VAR_B': 'b'}): - compose = DockerCompose(arrow_compose_path) - with assert_compose_calls(compose, expected_calls, env=expected_env): - assert os.environ['MY_CUSTOM_VAR_A'] == 'a' - assert os.environ['MY_CUSTOM_VAR_B'] == 'b' - compose.pull('conda-cpp') - compose.build('conda-cpp') - - -def test_compose_pull(arrow_compose_path): - compose = DockerCompose(arrow_compose_path) - - expected_calls = [ - "pull --ignore-pull-failures conda-cpp", - ] - with assert_compose_calls(compose, expected_calls): - compose.clear_pull_memory() - compose.pull('conda-cpp') - - expected_calls = [ - "pull --ignore-pull-failures conda-cpp", - "pull --ignore-pull-failures conda-python", - "pull --ignore-pull-failures conda-python-pandas" - ] - with assert_compose_calls(compose, expected_calls): - compose.clear_pull_memory() - compose.pull('conda-python-pandas') - - expected_calls = [ - "pull --ignore-pull-failures conda-cpp", - "pull --ignore-pull-failures conda-python", - ] - with assert_compose_calls(compose, expected_calls): - compose.clear_pull_memory() - compose.pull('conda-python-pandas', pull_leaf=False) - - -def test_compose_pull_params(arrow_compose_path): - expected_calls = [ - "pull --ignore-pull-failures conda-cpp", - "pull --ignore-pull-failures conda-python", - ] - compose = DockerCompose(arrow_compose_path, params=dict(UBUNTU='18.04')) - expected_env = PartialEnv(PYTHON='3.6', PANDAS='latest') - with assert_compose_calls(compose, expected_calls, env=expected_env): - compose.clear_pull_memory() - compose.pull('conda-python-pandas', pull_leaf=False) - - -def test_compose_build(arrow_compose_path): - compose = DockerCompose(arrow_compose_path) - - expected_calls = [ - "build conda-cpp", - ] - with assert_compose_calls(compose, expected_calls): - compose.build('conda-cpp') - - expected_calls = [ - "build --no-cache conda-cpp" - ] - with assert_compose_calls(compose, expected_calls): - compose.build('conda-cpp', use_cache=False) - - expected_calls = [ - "build conda-cpp", - "build conda-python", - "build conda-python-pandas" - ] - with assert_compose_calls(compose, expected_calls): - compose.build('conda-python-pandas') - - expected_calls = [ - "build --no-cache conda-cpp", - "build --no-cache conda-python", - "build --no-cache conda-python-pandas", - ] - with assert_compose_calls(compose, expected_calls): - compose.build('conda-python-pandas', use_cache=False) - - expected_calls = [ - "build conda-cpp", - "build conda-python", - "build --no-cache conda-python-pandas", - ] - with assert_compose_calls(compose, expected_calls): - compose.build('conda-python-pandas', use_cache=True, - use_leaf_cache=False) - - -@mock.patch.dict(os.environ, {"BUILDKIT_INLINE_CACHE": "1"}) -def test_compose_buildkit_inline_cache(arrow_compose_path): - compose = DockerCompose(arrow_compose_path) - - expected_calls = [ - "build --build-arg BUILDKIT_INLINE_CACHE=1 conda-cpp", - ] - with assert_compose_calls(compose, expected_calls): - compose.build('conda-cpp') - - -def test_compose_build_params(arrow_compose_path): - expected_calls = [ - "build ubuntu-cpp", - ] - - compose = DockerCompose(arrow_compose_path, params=dict(UBUNTU='18.04')) - expected_env = PartialEnv(UBUNTU="18.04") - with assert_compose_calls(compose, expected_calls, env=expected_env): - compose.build('ubuntu-cpp') - - compose = DockerCompose(arrow_compose_path, params=dict(UBUNTU='16.04')) - expected_env = PartialEnv(UBUNTU="16.04") - with assert_compose_calls(compose, expected_calls, env=expected_env): - compose.build('ubuntu-cpp') - - expected_calls = [ - "build --no-cache conda-cpp", - "build --no-cache conda-python", - "build --no-cache conda-python-pandas", - ] - compose = DockerCompose(arrow_compose_path, params=dict(UBUNTU='18.04')) - expected_env = PartialEnv(PYTHON='3.6', PANDAS='latest') - with assert_compose_calls(compose, expected_calls, env=expected_env): - compose.build('conda-python-pandas', use_cache=False) - - -def test_compose_run(arrow_compose_path): - expected_calls = [ - format_run("conda-cpp"), - ] - compose = DockerCompose(arrow_compose_path) - with assert_compose_calls(compose, expected_calls): - compose.run('conda-cpp') - - expected_calls = [ - format_run("conda-python") - ] - expected_env = PartialEnv(PYTHON='3.6') - with assert_compose_calls(compose, expected_calls, env=expected_env): - compose.run('conda-python') - - compose = DockerCompose(arrow_compose_path, params=dict(PYTHON='3.8')) - expected_env = PartialEnv(PYTHON='3.8') - with assert_compose_calls(compose, expected_calls, env=expected_env): - compose.run('conda-python') - - compose = DockerCompose(arrow_compose_path, params=dict(PYTHON='3.8')) - for command in ["bash", "echo 1"]: - expected_calls = [ - format_run(["conda-python", command]), - ] - expected_env = PartialEnv(PYTHON='3.8') - with assert_compose_calls(compose, expected_calls, env=expected_env): - compose.run('conda-python', command) - - expected_calls = [ - ( - format_run("-e CONTAINER_ENV_VAR_A=a -e CONTAINER_ENV_VAR_B=b " - "conda-python") - ) - ] - compose = DockerCompose(arrow_compose_path) - expected_env = PartialEnv(PYTHON='3.6') - with assert_compose_calls(compose, expected_calls, env=expected_env): - env = collections.OrderedDict([ - ("CONTAINER_ENV_VAR_A", "a"), - ("CONTAINER_ENV_VAR_B", "b") - ]) - compose.run('conda-python', env=env) - - expected_calls = [ - ( - format_run("--volume /host/build:/build --volume " - "/host/ccache:/ccache:delegated conda-python") - ) - ] - compose = DockerCompose(arrow_compose_path) - with assert_compose_calls(compose, expected_calls): - volumes = ("/host/build:/build", "/host/ccache:/ccache:delegated") - compose.run('conda-python', volumes=volumes) - - -def test_compose_push(arrow_compose_path): - compose = DockerCompose(arrow_compose_path, params=dict(PYTHON='3.8')) - expected_env = PartialEnv(PYTHON="3.8") - expected_calls = [ - mock.call(["docker", "login", "-u", "user", "-p", "pass"], check=True), - ] - for image in ["conda-cpp", "conda-python", "conda-python-pandas"]: - expected_calls.append( - mock.call(["docker-compose", "--file", str(compose.config.path), - "push", image], check=True, env=expected_env) - ) - with assert_subprocess_calls(expected_calls): - compose.push('conda-python-pandas', user='user', password='pass') - - -def test_compose_error(arrow_compose_path): - compose = DockerCompose(arrow_compose_path, params=dict( - PYTHON='3.8', - PANDAS='master' - )) - - error = subprocess.CalledProcessError(99, []) - with mock.patch('subprocess.run', side_effect=error): - with pytest.raises(RuntimeError) as exc: - compose.run('conda-cpp') - - exception_message = str(exc.value) - assert "exited with a non-zero exit code 99" in exception_message - assert "PANDAS: latest" in exception_message - assert "export PANDAS=master" in exception_message - - -def test_image_with_gpu(arrow_compose_path): - compose = DockerCompose(arrow_compose_path) - - expected_calls = [ - [ - "run", "--rm", "--gpus", "all", - "-e", "CUDA_ENV=1", - "-e", "OTHER_ENV=2", - "-v", "/host:/container:rw", - "org/ubuntu-cuda", - '/bin/bash -c "echo 1 > /tmp/dummy && cat /tmp/dummy"' - ] - ] - with assert_docker_calls(compose, expected_calls): - compose.run('ubuntu-cuda') - - -def test_listing_images(arrow_compose_path): - compose = DockerCompose(arrow_compose_path) - assert sorted(compose.images()) == [ - 'conda-cpp', - 'conda-python', - 'conda-python-dask', - 'conda-python-pandas', - 'ubuntu-c-glib', - 'ubuntu-cpp', - 'ubuntu-cpp-cmake32', - 'ubuntu-cuda', - 'ubuntu-ruby', - ] diff --git a/dev/archery/archery/tests/test_release.py b/dev/archery/archery/tests/test_release.py deleted file mode 100644 index 75aac8921232..000000000000 --- a/dev/archery/archery/tests/test_release.py +++ /dev/null @@ -1,333 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -import pytest - -from archery.release import ( - Release, MajorRelease, MinorRelease, PatchRelease, - Jira, Version, Issue, CommitTitle, Commit -) -from archery.testing import DotDict - - -# subset of issues per revision -_issues = { - "1.0.1": [ - Issue("ARROW-9684", type="Bug", summary="[C++] Title"), - Issue("ARROW-9667", type="New Feature", summary="[Crossbow] Title"), - Issue("ARROW-9659", type="Bug", summary="[C++] Title"), - Issue("ARROW-9644", type="Bug", summary="[C++][Dataset] Title"), - Issue("ARROW-9643", type="Bug", summary="[C++] Title"), - Issue("ARROW-9609", type="Bug", summary="[C++] Title"), - Issue("ARROW-9606", type="Bug", summary="[C++][Dataset] Title") - ], - "1.0.0": [ - Issue("ARROW-300", type="New Feature", summary="[Format] Title"), - Issue("ARROW-4427", type="Task", summary="[Doc] Title"), - Issue("ARROW-5035", type="Improvement", summary="[C#] Title"), - Issue("ARROW-8473", type="Bug", summary="[Rust] Title"), - Issue("ARROW-8472", type="Bug", summary="[Go][Integration] Title"), - Issue("ARROW-8471", type="Bug", summary="[C++][Integration] Title"), - Issue("ARROW-8974", type="Improvement", summary="[C++] Title"), - Issue("ARROW-8973", type="New Feature", summary="[Java] Title") - ], - "0.17.1": [ - Issue("ARROW-8684", type="Bug", summary="[Python] Title"), - Issue("ARROW-8657", type="Bug", summary="[C++][Parquet] Title"), - Issue("ARROW-8641", type="Bug", summary="[Python] Title"), - Issue("ARROW-8609", type="Bug", summary="[C++] Title"), - ], - "0.17.0": [ - Issue("ARROW-2882", type="New Feature", summary="[C++][Python] Title"), - Issue("ARROW-2587", type="Bug", summary="[Python] Title"), - Issue("ARROW-2447", type="Improvement", summary="[C++] Title"), - Issue("ARROW-2255", type="Bug", summary="[Integration] Title"), - Issue("ARROW-1907", type="Bug", summary="[C++/Python] Title"), - Issue("ARROW-1636", type="New Feature", summary="[Format] Title") - ] -} - - -class FakeJira(Jira): - - def __init__(self): - pass - - def project_versions(self, project='ARROW'): - return [ - Version.parse("3.0.0", released=False), - Version.parse("2.0.0", released=False), - Version.parse("1.1.0", released=False), - Version.parse("1.0.1", released=False), - Version.parse("1.0.0", released=True), - Version.parse("0.17.1", released=True), - Version.parse("0.17.0", released=True), - Version.parse("0.16.0", released=True), - Version.parse("0.15.2", released=True), - Version.parse("0.15.1", released=True), - Version.parse("0.15.0", released=True), - ] - - def project_issues(self, version, project='ARROW'): - return _issues[str(version)] - - -@pytest.fixture -def fake_jira(): - return FakeJira() - - -def test_version(fake_jira): - v = Version.parse("1.2.5") - assert str(v) == "1.2.5" - assert v.major == 1 - assert v.minor == 2 - assert v.patch == 5 - assert v.released is False - assert v.release_date is None - - v = Version.parse("1.0.0", released=True, release_date="2020-01-01") - assert str(v) == "1.0.0" - assert v.major == 1 - assert v.minor == 0 - assert v.patch == 0 - assert v.released is True - assert v.release_date == "2020-01-01" - - -def test_issue(fake_jira): - i = Issue("ARROW-1234", type='Bug', summary="title") - assert i.key == "ARROW-1234" - assert i.type == "Bug" - assert i.summary == "title" - assert i.project == "ARROW" - assert i.number == 1234 - - i = Issue("PARQUET-1111", type='Improvement', summary="another title") - assert i.key == "PARQUET-1111" - assert i.type == "Improvement" - assert i.summary == "another title" - assert i.project == "PARQUET" - assert i.number == 1111 - - fake_jira_issue = DotDict({ - 'key': 'ARROW-2222', - 'fields': { - 'issuetype': { - 'name': 'Feature' - }, - 'summary': 'Issue title' - } - }) - i = Issue.from_jira(fake_jira_issue) - assert i.key == "ARROW-2222" - assert i.type == "Feature" - assert i.summary == "Issue title" - assert i.project == "ARROW" - assert i.number == 2222 - - -def test_commit_title(): - t = CommitTitle.parse( - "ARROW-9598: [C++][Parquet] Fix writing nullable structs" - ) - assert t.project == "ARROW" - assert t.issue == "ARROW-9598" - assert t.components == ["C++", "Parquet"] - assert t.summary == "Fix writing nullable structs" - - t = CommitTitle.parse( - "ARROW-8002: [C++][Dataset][R] Support partitioned dataset writing" - ) - assert t.project == "ARROW" - assert t.issue == "ARROW-8002" - assert t.components == ["C++", "Dataset", "R"] - assert t.summary == "Support partitioned dataset writing" - - t = CommitTitle.parse( - "ARROW-9600: [Rust][Arrow] pin older version of proc-macro2 during " - "build" - ) - assert t.project == "ARROW" - assert t.issue == "ARROW-9600" - assert t.components == ["Rust", "Arrow"] - assert t.summary == "pin older version of proc-macro2 during build" - - t = CommitTitle.parse("[Release] Update versions for 1.0.0") - assert t.project is None - assert t.issue is None - assert t.components == ["Release"] - assert t.summary == "Update versions for 1.0.0" - - t = CommitTitle.parse("[Python][Doc] Fix rst role dataset.rst (#7725)") - assert t.project is None - assert t.issue is None - assert t.components == ["Python", "Doc"] - assert t.summary == "Fix rst role dataset.rst (#7725)" - - t = CommitTitle.parse( - "PARQUET-1882: [C++] Buffered Reads should allow for 0 length" - ) - assert t.project == 'PARQUET' - assert t.issue == 'PARQUET-1882' - assert t.components == ["C++"] - assert t.summary == "Buffered Reads should allow for 0 length" - - t = CommitTitle.parse( - "ARROW-9340 [R] Use CRAN version of decor package " - "\nsomething else\n" - "\nwhich should be truncated" - ) - assert t.project == 'ARROW' - assert t.issue == 'ARROW-9340' - assert t.components == ["R"] - assert t.summary == "Use CRAN version of decor package " - - -def test_release_basics(fake_jira): - r = Release.from_jira("1.0.0", jira=fake_jira) - assert isinstance(r, MajorRelease) - assert r.is_released is True - assert r.branch == 'master' - assert r.tag == 'apache-arrow-1.0.0' - - r = Release.from_jira("1.1.0", jira=fake_jira) - assert isinstance(r, MinorRelease) - assert r.is_released is False - assert r.branch == 'maint-1.x.x' - assert r.tag == 'apache-arrow-1.1.0' - - # minor releases before 1.0 are treated as major releases - r = Release.from_jira("0.17.0", jira=fake_jira) - assert isinstance(r, MajorRelease) - assert r.is_released is True - assert r.branch == 'master' - assert r.tag == 'apache-arrow-0.17.0' - - r = Release.from_jira("0.17.1", jira=fake_jira) - assert isinstance(r, PatchRelease) - assert r.is_released is True - assert r.branch == 'maint-0.17.x' - assert r.tag == 'apache-arrow-0.17.1' - - -def test_previous_and_next_release(fake_jira): - r = Release.from_jira("3.0.0", jira=fake_jira) - assert isinstance(r.previous, MajorRelease) - assert r.previous.version == Version.parse("2.0.0") - with pytest.raises(ValueError, match="There is no upcoming release set"): - assert r.next - - r = Release.from_jira("2.0.0", jira=fake_jira) - assert isinstance(r.previous, MajorRelease) - assert isinstance(r.next, MajorRelease) - assert r.previous.version == Version.parse("1.0.0") - assert r.next.version == Version.parse("3.0.0") - - r = Release.from_jira("1.1.0", jira=fake_jira) - assert isinstance(r.previous, MajorRelease) - assert isinstance(r.next, MajorRelease) - assert r.previous.version == Version.parse("1.0.0") - assert r.next.version == Version.parse("2.0.0") - - r = Release.from_jira("1.0.0", jira=fake_jira) - assert isinstance(r.next, MajorRelease) - assert isinstance(r.previous, MajorRelease) - assert r.previous.version == Version.parse("0.17.0") - assert r.next.version == Version.parse("2.0.0") - - r = Release.from_jira("0.17.0", jira=fake_jira) - assert isinstance(r.previous, MajorRelease) - assert r.previous.version == Version.parse("0.16.0") - - r = Release.from_jira("0.15.2", jira=fake_jira) - assert isinstance(r.previous, PatchRelease) - assert isinstance(r.next, MajorRelease) - assert r.previous.version == Version.parse("0.15.1") - assert r.next.version == Version.parse("0.16.0") - - r = Release.from_jira("0.15.1", jira=fake_jira) - assert isinstance(r.previous, MajorRelease) - assert isinstance(r.next, PatchRelease) - assert r.previous.version == Version.parse("0.15.0") - assert r.next.version == Version.parse("0.15.2") - - -def test_release_issues(fake_jira): - # major release issues - r = Release.from_jira("1.0.0", jira=fake_jira) - assert r.issues.keys() == set([ - "ARROW-300", - "ARROW-4427", - "ARROW-5035", - "ARROW-8473", - "ARROW-8472", - "ARROW-8471", - "ARROW-8974", - "ARROW-8973" - ]) - # minor release issues - r = Release.from_jira("0.17.0", jira=fake_jira) - assert r.issues.keys() == set([ - "ARROW-2882", - "ARROW-2587", - "ARROW-2447", - "ARROW-2255", - "ARROW-1907", - "ARROW-1636", - ]) - # patch release issues - r = Release.from_jira("1.0.1", jira=fake_jira) - assert r.issues.keys() == set([ - "ARROW-9684", - "ARROW-9667", - "ARROW-9659", - "ARROW-9644", - "ARROW-9643", - "ARROW-9609", - "ARROW-9606" - ]) - - -@pytest.mark.parametrize(('version', 'ncommits'), [ - ("1.0.0", 771), - ("0.17.1", 27), - ("0.17.0", 569), - ("0.15.1", 41) -]) -def test_release_commits(fake_jira, version, ncommits): - r = Release.from_jira(version, jira=fake_jira) - assert len(r.commits) == ncommits - for c in r.commits: - assert isinstance(c, Commit) - assert isinstance(c.title, CommitTitle) - assert c.url.endswith(c.hexsha) - - -def test_maintenance_patch_selection(fake_jira): - r = Release.from_jira("0.17.1", jira=fake_jira) - - shas_to_pick = [ - c.hexsha for c in r.commits_to_pick(exclude_already_applied=False) - ] - expected = [ - '8939b4bd446ee406d5225c79d563a27d30fd7d6d', - 'bcef6c95a324417e85e0140f9745d342cd8784b3', - '6002ec388840de5622e39af85abdc57a2cccc9b2', - '9123dadfd123bca7af4eaa9455f5b0d1ca8b929d', - ] - assert shas_to_pick == expected diff --git a/dev/archery/archery/tests/test_testing.py b/dev/archery/archery/tests/test_testing.py deleted file mode 100644 index 117b9288d74b..000000000000 --- a/dev/archery/archery/tests/test_testing.py +++ /dev/null @@ -1,62 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -import subprocess - -import pytest - -from archery.testing import PartialEnv, assert_subprocess_calls - - -def test_partial_env(): - assert PartialEnv(a=1, b=2) == {'a': 1, 'b': 2, 'c': 3} - assert PartialEnv(a=1) == {'a': 1, 'b': 2, 'c': 3} - assert PartialEnv(a=1, b=2) == {'a': 1, 'b': 2} - assert PartialEnv(a=1, b=2) != {'b': 2, 'c': 3} - assert PartialEnv(a=1, b=2) != {'a': 1, 'c': 3} - - -def test_assert_subprocess_calls(): - expected_calls = [ - "echo Hello", - ["echo", "World"] - ] - with assert_subprocess_calls(expected_calls): - subprocess.run(['echo', 'Hello']) - subprocess.run(['echo', 'World']) - - expected_env = PartialEnv( - CUSTOM_ENV_A='a', - CUSTOM_ENV_C='c' - ) - with assert_subprocess_calls(expected_calls, env=expected_env): - env = { - 'CUSTOM_ENV_A': 'a', - 'CUSTOM_ENV_B': 'b', - 'CUSTOM_ENV_C': 'c' - } - subprocess.run(['echo', 'Hello'], env=env) - subprocess.run(['echo', 'World'], env=env) - - with pytest.raises(AssertionError): - with assert_subprocess_calls(expected_calls, env=expected_env): - env = { - 'CUSTOM_ENV_B': 'b', - 'CUSTOM_ENV_C': 'c' - } - subprocess.run(['echo', 'Hello'], env=env) - subprocess.run(['echo', 'World'], env=env) diff --git a/dev/archery/archery/utils/__init__.py b/dev/archery/archery/utils/__init__.py deleted file mode 100644 index 13a83393a912..000000000000 --- a/dev/archery/archery/utils/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. diff --git a/dev/archery/archery/utils/cache.py b/dev/archery/archery/utils/cache.py deleted file mode 100644 index d92c5f32e270..000000000000 --- a/dev/archery/archery/utils/cache.py +++ /dev/null @@ -1,80 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -from pathlib import Path -import os -from urllib.request import urlopen - -from .logger import logger - -ARCHERY_CACHE_DIR = Path.home() / ".cache" / "archery" - - -class Cache: - """ Cache stores downloaded objects, notably apache-rat.jar. """ - - def __init__(self, path=ARCHERY_CACHE_DIR): - self.root = path - - if not path.exists(): - os.makedirs(path) - - def key_path(self, key): - """ Return the full path of a key. """ - return self.root/key - - def get(self, key): - """ Return the full path of a key if cached, None otherwise. """ - path = self.key_path(key) - return path if path.exists() else None - - def delete(self, key): - """ Remove a key (and the file) from the cache. """ - path = self.get(key) - if path: - path.unlink() - - def get_or_insert(self, key, create): - """ - Get or Insert a key from the cache. If the key is not found, the - `create` closure will be evaluated. - - The `create` closure takes a single parameter, the path where the - object should be store. The file should only be created upon success. - """ - path = self.key_path(key) - - if not path.exists(): - create(path) - - return path - - def get_or_insert_from_url(self, key, url): - """ - Get or Insert a key from the cache. If the key is not found, the file - is downloaded from `url`. - """ - def download(path): - """ Tiny wrapper that download a file and save as key. """ - logger.debug("Downloading {} as {}".format(url, path)) - conn = urlopen(url) - # Ensure the download is completed before writing to disks. - content = conn.read() - with open(path, "wb") as path_fd: - path_fd.write(content) - - return self.get_or_insert(key, download) diff --git a/dev/archery/archery/utils/cmake.py b/dev/archery/archery/utils/cmake.py deleted file mode 100644 index f93895b1a09c..000000000000 --- a/dev/archery/archery/utils/cmake.py +++ /dev/null @@ -1,215 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -import os -import re -from shutil import rmtree, which - -from .command import Command, default_bin - - -class CMake(Command): - def __init__(self, cmake_bin=None): - self.bin = default_bin(cmake_bin, "cmake") - - @staticmethod - def default_generator(): - """ Infer default generator. - - Gives precedence to ninja if there exists an executable named `ninja` - in the search path. - """ - found_ninja = which("ninja") - return "Ninja" if found_ninja else "Unix Makefiles" - - -cmake = CMake() - - -class CMakeDefinition: - """ CMakeDefinition captures the cmake invocation arguments. - - It allows creating build directories with the same definition, e.g. - ``` - build_1 = cmake_def.build("/tmp/build-1") - build_2 = cmake_def.build("/tmp/build-2") - - ... - - build1.all() - build2.all() - """ - - def __init__(self, source, build_type="release", generator=None, - definitions=None, env=None): - """ Initialize a CMakeDefinition - - Parameters - ---------- - source : str - Source directory where the top-level CMakeLists.txt is - located. This is usually the root of the project. - generator : str, optional - definitions: list(str), optional - env : dict(str,str), optional - Environment to use when invoking cmake. This can be required to - work around cmake deficiencies, e.g. CC and CXX. - """ - self.source = os.path.abspath(source) - self.build_type = build_type - self.generator = generator if generator else cmake.default_generator() - self.definitions = definitions if definitions else [] - self.env = env - - @property - def arguments(self): - """" Return the arguments to cmake invocation. """ - arguments = [ - "-G{}".format(self.generator), - ] + self.definitions + [ - self.source - ] - return arguments - - def build(self, build_dir, force=False, cmd_kwargs=None, **kwargs): - """ Invoke cmake into a build directory. - - Parameters - ---------- - build_dir : str - Directory in which the CMake build will be instantiated. - force : bool - If the build folder exists, delete it before. Otherwise if it's - present, an error will be returned. - """ - if os.path.exists(build_dir): - # Extra safety to ensure we're deleting a build folder. - if not CMakeBuild.is_build_dir(build_dir): - raise FileExistsError( - "{} is not a cmake build".format(build_dir) - ) - if not force: - raise FileExistsError( - "{} exists use force=True".format(build_dir) - ) - rmtree(build_dir) - - os.mkdir(build_dir) - - cmd_kwargs = cmd_kwargs if cmd_kwargs else {} - cmake(*self.arguments, cwd=build_dir, env=self.env, **cmd_kwargs) - return CMakeBuild(build_dir, self.build_type, definition=self, - **kwargs) - - def __repr__(self): - return "CMakeDefinition[source={}]".format(self.source) - - -CMAKE_BUILD_TYPE_RE = re.compile("CMAKE_BUILD_TYPE:STRING=([a-zA-Z]+)") - - -class CMakeBuild(CMake): - """ CMakeBuild represents a build directory initialized by cmake. - - The build instance can be used to build/test/install. It alleviates the - user to know which generator is used. - """ - - def __init__(self, build_dir, build_type, definition=None): - """ Initialize a CMakeBuild. - - The caller must ensure that cmake was invoked in the build directory. - - Parameters - ---------- - definition : CMakeDefinition - The definition to build from. - build_dir : str - The build directory to setup into. - """ - assert CMakeBuild.is_build_dir(build_dir) - super().__init__() - self.build_dir = os.path.abspath(build_dir) - self.build_type = build_type - self.definition = definition - - @property - def binaries_dir(self): - return os.path.join(self.build_dir, self.build_type) - - def run(self, *argv, verbose=False, **kwargs): - cmake_args = ["--build", self.build_dir, "--"] - extra = [] - if verbose: - extra.append("-v" if self.bin.endswith("ninja") else "VERBOSE=1") - # Commands must be ran under the build directory - return super().run(*cmake_args, *extra, - *argv, **kwargs, cwd=self.build_dir) - - def all(self): - return self.run("all") - - def clean(self): - return self.run("clean") - - def install(self): - return self.run("install") - - def test(self): - return self.run("test") - - @staticmethod - def is_build_dir(path): - """ Indicate if a path is CMake build directory. - - This method only checks for the existence of paths and does not do any - validation whatsoever. - """ - cmake_cache = os.path.join(path, "CMakeCache.txt") - cmake_files = os.path.join(path, "CMakeFiles") - return os.path.exists(cmake_cache) and os.path.exists(cmake_files) - - @staticmethod - def from_path(path): - """ Instantiate a CMakeBuild from a path. - - This is used to recover from an existing physical directory (created - with or without CMakeBuild). - - Note that this method is not idempotent as the original definition will - be lost. Only build_type is recovered. - """ - if not CMakeBuild.is_build_dir(path): - raise ValueError("Not a valid CMakeBuild path: {}".format(path)) - - build_type = None - # Infer build_type by looking at CMakeCache.txt and looking for a magic - # definition - cmake_cache_path = os.path.join(path, "CMakeCache.txt") - with open(cmake_cache_path, "r") as cmake_cache: - candidates = CMAKE_BUILD_TYPE_RE.findall(cmake_cache.read()) - build_type = candidates[0].lower() if candidates else "release" - - return CMakeBuild(path, build_type) - - def __repr__(self): - return ("CMakeBuild[" - "build = {}," - "build_type = {}," - "definition = {}]".format(self.build_dir, - self.build_type, - self.definition)) diff --git a/dev/archery/archery/utils/command.py b/dev/archery/archery/utils/command.py deleted file mode 100644 index 84d2842073f3..000000000000 --- a/dev/archery/archery/utils/command.py +++ /dev/null @@ -1,97 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -import os -import shlex -import shutil -import subprocess - -from .logger import logger, ctx - - -def default_bin(name, default): - assert(default) - env_name = "ARCHERY_{0}_BIN".format(default.upper()) - return name if name else os.environ.get(env_name, default) - - -# Decorator running a command and returning stdout -class capture_stdout: - def __init__(self, strip=False, listify=False): - self.strip = strip - self.listify = listify - - def __call__(self, f): - def strip_it(x): - return x.strip() if self.strip else x - - def list_it(x): - return x.decode('utf-8').splitlines() if self.listify else x - - def wrapper(*argv, **kwargs): - # Ensure stdout is captured - kwargs["stdout"] = subprocess.PIPE - return list_it(strip_it(f(*argv, **kwargs).stdout)) - return wrapper - - -class Command: - """ A runnable command. - - Class inheriting from the Command class must provide the bin - property/attribute. - """ - - def __init__(self, bin): - self.bin = bin - - def run(self, *argv, **kwargs): - assert hasattr(self, "bin") - invocation = shlex.split(self.bin) - invocation.extend(argv) - - for key in ["stdout", "stderr"]: - # Preserve caller intention, otherwise silence - if key not in kwargs and ctx.quiet: - kwargs[key] = subprocess.PIPE - - # Prefer safe by default - if "check" not in kwargs: - kwargs["check"] = True - - logger.debug("Executing `{}`".format(invocation)) - return subprocess.run(invocation, **kwargs) - - @property - def available(self): - """ Indicate if the command binary is found in PATH. """ - binary = shlex.split(self.bin)[0] - return shutil.which(binary) is not None - - def __call__(self, *argv, **kwargs): - return self.run(*argv, **kwargs) - - -class CommandStackMixin: - def run(self, *argv, **kwargs): - stacked_args = self.argv + argv - return super(CommandStackMixin, self).run(*stacked_args, **kwargs) - - -class Bash(Command): - def __init__(self, bash_bin=None): - self.bin = default_bin(bash_bin, "bash") diff --git a/dev/archery/archery/utils/git.py b/dev/archery/archery/utils/git.py deleted file mode 100644 index 798bc5d7096f..000000000000 --- a/dev/archery/archery/utils/git.py +++ /dev/null @@ -1,100 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -from .command import Command, capture_stdout, default_bin -from ..compat import _stringify_path - - -# Decorator prepending argv with the git sub-command found with the method -# name. -def git_cmd(fn): - # function name is the subcommand - sub_cmd = fn.__name__.replace("_", "-") - - def wrapper(self, *argv, **kwargs): - return fn(self, sub_cmd, *argv, **kwargs) - return wrapper - - -class Git(Command): - def __init__(self, git_bin=None): - self.bin = default_bin(git_bin, "git") - - def run_cmd(self, cmd, *argv, git_dir=None, **kwargs): - """ Inject flags before sub-command in argv. """ - opts = [] - if git_dir is not None: - opts.extend(["-C", _stringify_path(git_dir)]) - - return self.run(*opts, cmd, *argv, **kwargs) - - @capture_stdout(strip=False) - @git_cmd - def archive(self, *argv, **kwargs): - return self.run_cmd(*argv, **kwargs) - - @git_cmd - def clone(self, *argv, **kwargs): - return self.run_cmd(*argv, **kwargs) - - @git_cmd - def fetch(self, *argv, **kwargs): - return self.run_cmd(*argv, **kwargs) - - @git_cmd - def checkout(self, *argv, **kwargs): - return self.run_cmd(*argv, **kwargs) - - def dirty(self, **kwargs): - return len(self.status("--short", **kwargs)) > 0 - - @git_cmd - def log(self, *argv, **kwargs): - return self.run_cmd(*argv, **kwargs) - - @capture_stdout(strip=True, listify=True) - @git_cmd - def ls_files(self, *argv, listify=False, **kwargs): - stdout = self.run_cmd(*argv, **kwargs) - return stdout - - @capture_stdout(strip=True) - @git_cmd - def rev_parse(self, *argv, **kwargs): - return self.run_cmd(*argv, **kwargs) - - @capture_stdout(strip=True) - @git_cmd - def status(self, *argv, **kwargs): - return self.run_cmd(*argv, **kwargs) - - @capture_stdout(strip=True) - def head(self, **kwargs): - """ Return commit pointed by HEAD. """ - return self.rev_parse("HEAD", **kwargs) - - @capture_stdout(strip=True) - def current_branch(self, **kwargs): - return self.rev_parse("--abbrev-ref", "HEAD", **kwargs) - - def repository_root(self, git_dir=None, **kwargs): - """ Locates the repository's root path from a subdirectory. """ - stdout = self.rev_parse("--show-toplevel", git_dir=git_dir, **kwargs) - return stdout.decode('utf-8') - - -git = Git() diff --git a/dev/archery/archery/utils/lint.py b/dev/archery/archery/utils/lint.py deleted file mode 100644 index e81d6ac869dd..000000000000 --- a/dev/archery/archery/utils/lint.py +++ /dev/null @@ -1,383 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -import gzip -import os -from pathlib import Path - -import click - -from .command import Bash, Command, default_bin -from .cmake import CMake -from .git import git -from .logger import logger -from ..lang.cpp import CppCMakeDefinition, CppConfiguration -from ..lang.rust import Cargo -from ..lang.python import Autopep8, Flake8, NumpyDoc -from .rat import Rat, exclusion_from_globs -from .tmpdir import tmpdir - - -class LintValidationException(Exception): - pass - - -class LintResult: - def __init__(self, success, reason=None): - self.success = success - - def ok(self): - if not self.success: - raise LintValidationException - - @staticmethod - def from_cmd(command_result): - return LintResult(command_result.returncode == 0) - - -def cpp_linter(src, build_dir, clang_format=True, cpplint=True, - clang_tidy=False, iwyu=False, iwyu_all=False, - fix=False): - """ Run clang-format, cpplint and clang-tidy on cpp/ codebase. """ - logger.info("Running C++ linters") - - cmake = CMake() - if not cmake.available: - logger.error("cpp linter requested but cmake binary not found.") - return - - # A cmake build directory is required to populate `compile_commands.json` - # which in turn is required by clang-tidy. It also provides a convenient - # way to hide clang-format/clang-tidy invocation via the Generate - # (ninja/make) targets. - - # ARROW_LINT_ONLY exits early but ignore building compile_command.json - lint_only = not (iwyu or clang_tidy) - cmake_args = {"with_python": False, "with_lint_only": lint_only} - cmake_def = CppCMakeDefinition(src.cpp, CppConfiguration(**cmake_args)) - - build = cmake_def.build(build_dir) - if clang_format: - target = "format" if fix else "check-format" - yield LintResult.from_cmd(build.run(target, check=False)) - - if cpplint: - yield LintResult.from_cmd(build.run("lint", check=False)) - yield LintResult.from_cmd(build.run("lint_cpp_cli", check=False)) - - if clang_tidy: - yield LintResult.from_cmd(build.run("check-clang-tidy", check=False)) - - if iwyu: - if iwyu_all: - iwyu_cmd = "iwyu-all" - else: - iwyu_cmd = "iwyu" - yield LintResult.from_cmd(build.run(iwyu_cmd, check=False)) - - -class CMakeFormat(Command): - def __init__(self, cmake_format_bin): - self.bin = cmake_format_bin - - -def cmake_linter(src, fix=False): - """ Run cmake-format.py on all CMakeFiles.txt """ - logger.info("Running cmake-format linters") - - if not fix: - logger.warn("run-cmake-format modifies files, regardless of --fix") - - arrow_cmake_format = os.path.join(src.path, "run-cmake-format.py") - cmake_format = CMakeFormat(cmake_format_bin=arrow_cmake_format) - yield LintResult.from_cmd(cmake_format("--check")) - - -def python_linter(src, fix=False): - """Run Python linters on python/pyarrow, python/examples, setup.py - and dev/. """ - setup_py = os.path.join(src.python, "setup.py") - setup_cfg = os.path.join(src.python, "setup.cfg") - - logger.info("Running Python formatter (autopep8)") - - autopep8 = Autopep8() - if not autopep8.available: - logger.error( - "Python formatter requested but autopep8 binary not found. " - "Please run `pip install -r dev/archery/requirements-lint.txt`") - return - - # Gather files for autopep8 - patterns = ["python/pyarrow/**/*.py", - "python/pyarrow/**/*.pyx", - "python/pyarrow/**/*.pxd", - "python/pyarrow/**/*.pxi", - "python/examples/**/*.py", - "dev/archery/**/*.py", - ] - files = [setup_py] - for pattern in patterns: - files += list(map(str, Path(src.path).glob(pattern))) - - args = ['--global-config', setup_cfg, '--ignore-local-config'] - if fix: - args += ['-j0', '--in-place'] - args += sorted(files) - yield LintResult.from_cmd(autopep8(*args)) - else: - # XXX `-j0` doesn't work well with `--exit-code`, so instead - # we capture the diff and check whether it's empty - # (https://github.com/hhatto/autopep8/issues/543) - args += ['-j0', '--diff'] - args += sorted(files) - diff = autopep8.run_captured(*args) - if diff: - print(diff.decode('utf8')) - yield LintResult(success=False) - else: - yield LintResult(success=True) - - # Run flake8 after autopep8 (the latter may have modified some files) - logger.info("Running Python linter (flake8)") - - flake8 = Flake8() - if not flake8.available: - logger.error( - "Python linter requested but flake8 binary not found. " - "Please run `pip install -r dev/archery/requirements-lint.txt`") - return - - flake8_exclude = ['.venv*'] - - yield LintResult.from_cmd( - flake8("--extend-exclude=" + ','.join(flake8_exclude), - setup_py, src.pyarrow, os.path.join(src.python, "examples"), - src.dev, check=False)) - config = os.path.join(src.python, ".flake8.cython") - yield LintResult.from_cmd( - flake8("--config=" + config, src.pyarrow, check=False)) - - -def python_numpydoc(symbols=None, allow_rules=None, disallow_rules=None): - """Run numpydoc linter on python. - - Pyarrow must be available for import. - """ - logger.info("Running Python docstring linters") - # by default try to run on all pyarrow package - symbols = symbols or { - 'pyarrow', - 'pyarrow.compute', - 'pyarrow.csv', - 'pyarrow.dataset', - 'pyarrow.feather', - 'pyarrow.flight', - 'pyarrow.fs', - 'pyarrow.gandiva', - 'pyarrow.ipc', - 'pyarrow.json', - 'pyarrow.orc', - 'pyarrow.parquet', - 'pyarrow.plasma', - 'pyarrow.types', - } - try: - numpydoc = NumpyDoc(symbols) - except RuntimeError as e: - logger.error(str(e)) - yield LintResult(success=False) - return - - results = numpydoc.validate( - # limit the validation scope to the pyarrow package - from_package='pyarrow', - allow_rules=allow_rules, - disallow_rules=disallow_rules - ) - - if len(results) == 0: - yield LintResult(success=True) - return - - number_of_violations = 0 - for obj, result in results: - errors = result['errors'] - - # inspect doesn't play nice with cython generated source code, - # to use a hacky way to represent a proper __qualname__ - doc = getattr(obj, '__doc__', '') - name = getattr(obj, '__name__', '') - qualname = getattr(obj, '__qualname__', '') - module = getattr(obj, '__module__', '') - instance = getattr(obj, '__self__', '') - if instance: - klass = instance.__class__.__name__ - else: - klass = '' - - try: - cython_signature = doc.splitlines()[0] - except Exception: - cython_signature = '' - - desc = '.'.join(filter(None, [module, klass, qualname or name])) - - click.echo() - click.echo(click.style(desc, bold=True, fg='yellow')) - if cython_signature: - qualname_with_signature = '.'.join([module, cython_signature]) - click.echo( - click.style( - '-> {}'.format(qualname_with_signature), - fg='yellow' - ) - ) - - for error in errors: - number_of_violations += 1 - click.echo('{}: {}'.format(*error)) - - msg = 'Total number of docstring violations: {}'.format( - number_of_violations - ) - click.echo() - click.echo(click.style(msg, fg='red')) - - yield LintResult(success=False) - - -def rat_linter(src, root): - """Run apache-rat license linter.""" - logger.info("Running apache-rat linter") - - exclusion = exclusion_from_globs( - os.path.join(src.dev, "release", "rat_exclude_files.txt")) - - # Creates a git-archive of ArrowSources, apache-rat expects a gzip - # compressed tar archive. - archive_path = os.path.join(root, "apache-arrow.tar.gz") - src.archive(archive_path, compressor=gzip.compress) - report = Rat().report(archive_path) - - violations = list(report.validate(exclusion=exclusion)) - for violation in violations: - print("apache-rat license violation: {}".format(violation)) - - yield LintResult(len(violations) == 0) - - -def r_linter(src): - """Run R linter.""" - logger.info("Running R linter") - r_lint_sh = os.path.join(src.r, "lint.sh") - yield LintResult.from_cmd(Bash().run(r_lint_sh, check=False)) - - -def rust_linter(src): - """Run Rust linter.""" - logger.info("Running Rust linter") - cargo = Cargo() - - if not cargo.available: - logger.error("Rust linter requested but cargo executable not found.") - return - - yield LintResult.from_cmd(cargo.run("+stable", "fmt", "--all", "--", - "--check", cwd=src.rust, - check=False)) - - -class Hadolint(Command): - def __init__(self, hadolint_bin=None): - self.bin = default_bin(hadolint_bin, "hadolint") - - -def is_docker_image(path): - dirname = os.path.dirname(path) - filename = os.path.basename(path) - - excluded = dirname.startswith( - "dev") or dirname.startswith("python/manylinux") - - return filename.startswith("Dockerfile") and not excluded - - -def docker_linter(src): - """Run Hadolint docker linter.""" - logger.info("Running Docker linter") - - hadolint = Hadolint() - - if not hadolint.available: - logger.error( - "hadolint linter requested but hadolint binary not found.") - return - - for path in git.ls_files(git_dir=src.path): - if is_docker_image(path): - yield LintResult.from_cmd(hadolint.run(path, check=False, - cwd=src.path)) - - -def linter(src, fix=False, *, clang_format=False, cpplint=False, - clang_tidy=False, iwyu=False, iwyu_all=False, - python=False, numpydoc=False, cmake_format=False, rat=False, - r=False, rust=False, docker=False): - """Run all linters.""" - with tmpdir(prefix="arrow-lint-") as root: - build_dir = os.path.join(root, "cpp-build") - - # Linters yield LintResult without raising exceptions on failure. - # This allows running all linters in one pass and exposing all - # errors to the user. - results = [] - - if clang_format or cpplint or clang_tidy or iwyu: - results.extend(cpp_linter(src, build_dir, - clang_format=clang_format, - cpplint=cpplint, - clang_tidy=clang_tidy, - iwyu=iwyu, - iwyu_all=iwyu_all, - fix=fix)) - - if python: - results.extend(python_linter(src, fix=fix)) - - if numpydoc: - results.extend(python_numpydoc()) - - if cmake_format: - results.extend(cmake_linter(src, fix=fix)) - - if rat: - results.extend(rat_linter(src, root)) - - if r: - results.extend(r_linter(src)) - - if rust: - results.extend(rust_linter(src)) - - if docker: - results.extend(docker_linter(src)) - - # Raise error if one linter failed, ensuring calling code can exit with - # non-zero. - for result in results: - result.ok() diff --git a/dev/archery/archery/utils/logger.py b/dev/archery/archery/utils/logger.py deleted file mode 100644 index 9d0feda88e6e..000000000000 --- a/dev/archery/archery/utils/logger.py +++ /dev/null @@ -1,29 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -import logging - -""" Global logger. """ -logger = logging.getLogger("archery") - - -class LoggingContext: - def __init__(self, quiet=False): - self.quiet = quiet - - -ctx = LoggingContext() diff --git a/dev/archery/archery/utils/rat.py b/dev/archery/archery/utils/rat.py deleted file mode 100644 index e7fe19a7ea8c..000000000000 --- a/dev/archery/archery/utils/rat.py +++ /dev/null @@ -1,70 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -import fnmatch -import re -from xml.etree import ElementTree - -from ..lang.java import Jar -from .cache import Cache -from .command import capture_stdout - -RAT_VERSION = 0.13 -RAT_JAR_FILENAME = "apache-rat-{}.jar".format(RAT_VERSION) -RAT_URL_ = "https://repo1.maven.org/maven2/org/apache/rat/apache-rat" -RAT_URL = "/".join([RAT_URL_, str(RAT_VERSION), RAT_JAR_FILENAME]) - - -class Rat(Jar): - def __init__(self): - jar = Cache().get_or_insert_from_url(RAT_JAR_FILENAME, RAT_URL) - Jar.__init__(self, jar) - - @capture_stdout(strip=False) - def run_report(self, archive_path, **kwargs): - return self.run("--xml", archive_path, **kwargs) - - def report(self, archive_path, **kwargs): - return RatReport(self.run_report(archive_path, **kwargs)) - - -def exclusion_from_globs(exclusions_path): - with open(exclusions_path, 'r') as exclusions_fd: - exclusions = [e.strip() for e in exclusions_fd] - return lambda path: any([fnmatch.fnmatch(path, e) for e in exclusions]) - - -class RatReport: - def __init__(self, xml): - self.xml = xml - self.tree = ElementTree.fromstring(xml) - - def __repr__(self): - return "RatReport({})".format(self.xml) - - def validate(self, exclusion=None): - for r in self.tree.findall('resource'): - approvals = r.findall('license-approval') - if not approvals or approvals[0].attrib['name'] == 'true': - continue - - clean_name = re.sub('^[^/]+/', '', r.attrib['name']) - - if exclusion and exclusion(clean_name): - continue - - yield clean_name diff --git a/dev/archery/archery/utils/report.py b/dev/archery/archery/utils/report.py deleted file mode 100644 index 6c7587ddd872..000000000000 --- a/dev/archery/archery/utils/report.py +++ /dev/null @@ -1,64 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -from abc import ABCMeta, abstractmethod -import datetime - -import jinja2 - - -def markdown_escape(s): - for char in ('*', '#', '_', '~', '`', '>'): - s = s.replace(char, '\\' + char) - return s - - -class Report(metaclass=ABCMeta): - - def __init__(self, **kwargs): - for field in self.fields: - if field not in kwargs: - raise ValueError('Missing keyword argument {}'.format(field)) - self._data = kwargs - - def __getattr__(self, key): - return self._data[key] - - @abstractmethod - def fields(self): - pass - - @property - @abstractmethod - def templates(self): - pass - - -class JinjaReport(Report): - - def __init__(self, **kwargs): - self.env = jinja2.Environment( - loader=jinja2.PackageLoader('archery', 'templates') - ) - self.env.filters['md'] = markdown_escape - self.env.globals['today'] = datetime.date.today - super().__init__(**kwargs) - - def render(self, template_name): - template_path = self.templates[template_name] - template = self.env.get_template(template_path) - return template.render(**self._data) diff --git a/dev/archery/archery/utils/source.py b/dev/archery/archery/utils/source.py deleted file mode 100644 index 1ae0fe025049..000000000000 --- a/dev/archery/archery/utils/source.py +++ /dev/null @@ -1,205 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -import os -from pathlib import Path -import subprocess - -from .git import git - - -class InvalidArrowSource(Exception): - pass - - -class ArrowSources: - """ ArrowSources is a companion class representing a directory containing - Apache Arrow's sources. - """ - # Note that WORKSPACE is a reserved git revision name by this module to - # reference the current git workspace. In other words, this indicates to - # ArrowSources.at_revision that no cloning/checkout is required. - WORKSPACE = "WORKSPACE" - - def __init__(self, path): - """ Initialize an ArrowSources - - The caller must ensure that path is valid arrow source directory (can - be checked with ArrowSources.valid) - - Parameters - ---------- - path : src - """ - self.path = Path(path) - - @property - def archery(self): - """ Returns the archery directory of an Arrow sources. """ - return self.dev / "archery" - - @property - def cpp(self): - """ Returns the cpp directory of an Arrow sources. """ - return self.path / "cpp" - - @property - def dev(self): - """ Returns the dev directory of an Arrow sources. """ - return self.path / "dev" - - @property - def python(self): - """ Returns the python directory of an Arrow sources. """ - return self.path / "python" - - @property - def pyarrow(self): - """ Returns the python/pyarrow directory of an Arrow sources. """ - return self.python / "pyarrow" - - @property - def r(self): - """ Returns the r directory of an Arrow sources. """ - return self.path / "r" - - @property - def rust(self): - """ Returns the rust directory of an Arrow sources. """ - return self.path / "rust" - - @property - def git_backed(self): - """ Indicate if the sources are backed by git. """ - return (self.path / ".git").exists() - - @property - def git_dirty(self): - """ Indicate if the sources is a dirty git directory. """ - return self.git_backed and git.dirty(git_dir=self.path) - - def archive(self, path, dereference=False, compressor=None, revision=None): - """ Saves a git archive at path. """ - if not self.git_backed: - raise ValueError("{} is not backed by git".format(self)) - - rev = revision if revision else "HEAD" - archive = git.archive("--prefix=apache-arrow/", rev, - git_dir=self.path) - - # TODO(fsaintjacques): fix dereference for - - if compressor: - archive = compressor(archive) - - with open(path, "wb") as archive_fd: - archive_fd.write(archive) - - def at_revision(self, revision, clone_dir): - """ Return a copy of the current sources for a specified git revision. - - This method may return the current object if no checkout is required. - The caller is responsible to remove the cloned repository directory. - - The user can use the special WORKSPACE token to mean the current git - workspace (no checkout performed). - - The second value of the returned tuple indicates if a clone was - performed. - - Parameters - ---------- - revision : str - Revision to checkout sources at. - clone_dir : str - Path to checkout the local clone. - """ - if not self.git_backed: - raise ValueError("{} is not backed by git".format(self)) - - if revision == ArrowSources.WORKSPACE: - return self, False - - # A local clone is required to leave the current sources intact such - # that builds depending on said sources are not invalidated (or worse - # slightly affected when re-invoking the generator). - # "--local" only works when dest dir is on same volume of source dir. - # "--shared" works even if dest dir is on different volume. - git.clone("--shared", self.path, clone_dir) - - # Revision can reference "origin/" (or any remotes) that are not found - # in the local clone. Thus, revisions are dereferenced in the source - # repository. - original_revision = git.rev_parse(revision) - - git.checkout(original_revision, git_dir=clone_dir) - - return ArrowSources(clone_dir), True - - @staticmethod - def find(path=None): - """ Infer Arrow sources directory from various method. - - The following guesses are done in order until a valid match is found: - - 1. Checks the given optional parameter. - - 2. Checks if the environment variable `ARROW_SRC` is defined and use - this. - - 3. Checks if the current working directory (cwd) is an Arrow source - directory. - - 4. Checks if this file (cli.py) is still in the original source - repository. If so, returns the relative path to the source - directory. - """ - - # Explicit via environment - env = os.environ.get("ARROW_SRC") - - # Implicit via cwd - cwd = Path.cwd() - - # Implicit via current file - try: - this = Path(__file__).parents[4] - except IndexError: - this = None - - # Implicit via git repository (if archery is installed system wide) - try: - repo = git.repository_root(git_dir=cwd) - except subprocess.CalledProcessError: - # We're not inside a git repository. - repo = None - - paths = list(filter(None, [path, env, cwd, this, repo])) - for p in paths: - try: - return ArrowSources(p) - except InvalidArrowSource: - pass - - searched_paths = "\n".join([" - {}".format(p) for p in paths]) - raise InvalidArrowSource( - "Unable to locate Arrow's source directory. " - "Searched paths are:\n{}".format(searched_paths) - ) - - def __repr__(self): - return self.path diff --git a/dev/archery/archery/utils/tmpdir.py b/dev/archery/archery/utils/tmpdir.py deleted file mode 100644 index 07d7355c87fb..000000000000 --- a/dev/archery/archery/utils/tmpdir.py +++ /dev/null @@ -1,28 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -from contextlib import contextmanager -from tempfile import mkdtemp, TemporaryDirectory - - -@contextmanager -def tmpdir(preserve=False, prefix="arrow-archery-"): - if preserve: - yield mkdtemp(prefix=prefix) - else: - with TemporaryDirectory(prefix=prefix) as tmp: - yield tmp diff --git a/dev/archery/conftest.py b/dev/archery/conftest.py deleted file mode 100644 index 06a643bea564..000000000000 --- a/dev/archery/conftest.py +++ /dev/null @@ -1,70 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -import pathlib - -import pytest - - -def pytest_addoption(parser): - parser.addoption( - "--enable-integration", - action="store_true", - default=False, - help="run slow tests" - ) - - -def pytest_configure(config): - config.addinivalue_line( - "markers", - ( - "integration: mark test as integration tests involving more " - "extensive setup (only used for crossbow at the moment)" - ) - ) - - -def pytest_collection_modifyitems(config, items): - if config.getoption("--enable-integration"): - return - marker = pytest.mark.skip(reason="need --enable-integration option to run") - for item in items: - if "integration" in item.keywords: - item.add_marker(marker) - - -@pytest.fixture -def load_fixture(request): - current_test_directory = pathlib.Path(request.node.fspath).parent - - def decoder(path): - with path.open('r') as fp: - if path.suffix == '.json': - import json - return json.load(fp) - elif path.suffix == '.yaml': - import yaml - return yaml.load(fp) - else: - return fp.read() - - def loader(name, decoder=decoder): - path = current_test_directory / 'fixtures' / name - return decoder(path) - - return loader diff --git a/dev/archery/generate_files_for_endian_test.sh b/dev/archery/generate_files_for_endian_test.sh deleted file mode 100755 index 54019ea570e2..000000000000 --- a/dev/archery/generate_files_for_endian_test.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -# This script generates json and arrow files of each type (e.g. primitive) for integration endian test -# Usage: generate_files_for_endian_test.sh -# ARROW_CPP_EXE_PATH : where Arrow C++ binaries can be found -# TMP_DIR : where files will be generated - -set -e - -: ${ARROW_CPP_EXE_PATH:=/arrow/cpp/build/debug/} -: ${TMP_DIR:=/tmp/arrow} - -json_dir=$TMP_DIR/arrow.$$ -mkdir -p $json_dir - -archery integration --stop-on-error --with-cpp=1 --tempdir=$json_dir - -for f in $json_dir/*.json ; do - $ARROW_CPP_EXE_PATH/arrow-json-integration-test -mode JSON_TO_ARROW -json $f -arrow ${f%.*}.arrow_file -integration true ; -done -for f in $json_dir/*.arrow_file ; do - $ARROW_CPP_EXE_PATH/arrow-file-to-stream $f > ${f%.*}.stream; -done -for f in $json_dir/*.json ; do - gzip $f ; -done -echo "The files are under $json_dir" diff --git a/dev/archery/requirements-lint.txt b/dev/archery/requirements-lint.txt deleted file mode 100644 index fc7f339ed4db..000000000000 --- a/dev/archery/requirements-lint.txt +++ /dev/null @@ -1,3 +0,0 @@ -autopep8 -flake8 -cmake_format==0.5.2 diff --git a/dev/archery/requirements.txt b/dev/archery/requirements.txt deleted file mode 100644 index 0e1258adbb63..000000000000 --- a/dev/archery/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -click -pygithub -python-dotenv -ruamel.yaml diff --git a/dev/archery/setup.py b/dev/archery/setup.py deleted file mode 100755 index 0537e8b4d311..000000000000 --- a/dev/archery/setup.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env python -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -import functools -import operator -import sys -from setuptools import setup - -if sys.version_info < (3, 6): - sys.exit('Python < 3.6 is not supported') - -# For pathlib.Path compatibility -jinja_req = 'jinja2>=2.11' - -extras = { - 'benchmark': ['pandas'], - 'docker': ['ruamel.yaml', 'python-dotenv'], - 'release': [jinja_req, 'jira', 'semver', 'gitpython'], - 'crossbow': ['github3.py', jinja_req, 'pygit2', 'ruamel.yaml', - 'setuptools_scm'], -} -extras['bot'] = extras['crossbow'] + ['pygithub', 'jira'] -extras['all'] = list(set(functools.reduce(operator.add, extras.values()))) - -setup( - name='archery', - version="0.1.0", - description='Apache Arrow Developers Tools', - url='http://github.com/apache/arrow', - maintainer='Arrow Developers', - maintainer_email='dev@arrow.apache.org', - packages=[ - 'archery', - 'archery.benchmark', - 'archery.integration', - 'archery.lang', - 'archery.utils' - ], - include_package_data=True, - install_requires=['click>=7'], - tests_require=['pytest', 'responses'], - extras_require=extras, - entry_points=''' - [console_scripts] - archery=archery.cli:archery - ''' -) diff --git a/dev/release/rat_exclude_files.txt b/dev/release/rat_exclude_files.txt index f3eb273adff4..d2b5aadbb000 100644 --- a/dev/release/rat_exclude_files.txt +++ b/dev/release/rat_exclude_files.txt @@ -1,94 +1,14 @@ *.npmrc *.gitignore .gitmodules -*_generated.h -*_generated.js -*_generated.ts *.csv *.json *.snap .github/ISSUE_TEMPLATE/*.md .github/pull_request_template.md -ci/etc/rprofile -ci/etc/*.patch -ci/vcpkg/*.patch CHANGELOG.md -dev/requirements*.txt -dev/archery/MANIFEST.in -dev/archery/requirements*.txt -dev/archery/archery/tests/fixtures/* -dev/archery/archery/crossbow/tests/fixtures/* -dev/release/rat_exclude_files.txt -dev/tasks/homebrew-formulae/apache-arrow.rb -dev/tasks/linux-packages/apache-arrow-apt-source/debian/apache-arrow-apt-source.install -dev/tasks/linux-packages/apache-arrow-apt-source/debian/compat -dev/tasks/linux-packages/apache-arrow-apt-source/debian/control -dev/tasks/linux-packages/apache-arrow-apt-source/debian/rules -dev/tasks/linux-packages/apache-arrow-apt-source/debian/source/format -dev/tasks/linux-packages/apache-arrow/debian/compat -dev/tasks/linux-packages/apache-arrow/debian/control.in -dev/tasks/linux-packages/apache-arrow/debian/gir1.2-arrow-1.0.install -dev/tasks/linux-packages/apache-arrow/debian/gir1.2-arrow-cuda-1.0.install -dev/tasks/linux-packages/apache-arrow/debian/gir1.2-arrow-dataset-1.0.install -dev/tasks/linux-packages/apache-arrow/debian/gir1.2-gandiva-1.0.install -dev/tasks/linux-packages/apache-arrow/debian/gir1.2-parquet-1.0.install -dev/tasks/linux-packages/apache-arrow/debian/gir1.2-plasma-1.0.install -dev/tasks/linux-packages/apache-arrow/debian/libarrow-dev.install -dev/tasks/linux-packages/apache-arrow/debian/libarrow-glib-dev.install -dev/tasks/linux-packages/apache-arrow/debian/libarrow-glib-doc.doc-base -dev/tasks/linux-packages/apache-arrow/debian/libarrow-glib-doc.install -dev/tasks/linux-packages/apache-arrow/debian/libarrow-glib-doc.links -dev/tasks/linux-packages/apache-arrow/debian/libarrow-glib400.install -dev/tasks/linux-packages/apache-arrow/debian/libarrow-cuda-dev.install -dev/tasks/linux-packages/apache-arrow/debian/libarrow-cuda-glib-dev.install -dev/tasks/linux-packages/apache-arrow/debian/libarrow-cuda-glib400.install -dev/tasks/linux-packages/apache-arrow/debian/libarrow-cuda400.install -dev/tasks/linux-packages/apache-arrow/debian/libarrow-dataset-dev.install -dev/tasks/linux-packages/apache-arrow/debian/libarrow-dataset-glib-dev.install -dev/tasks/linux-packages/apache-arrow/debian/libarrow-dataset-glib-doc.doc-base -dev/tasks/linux-packages/apache-arrow/debian/libarrow-dataset-glib-doc.install -dev/tasks/linux-packages/apache-arrow/debian/libarrow-dataset-glib-doc.links -dev/tasks/linux-packages/apache-arrow/debian/libarrow-dataset-glib400.install -dev/tasks/linux-packages/apache-arrow/debian/libarrow-dataset400.install -dev/tasks/linux-packages/apache-arrow/debian/libarrow-flight-dev.install -dev/tasks/linux-packages/apache-arrow/debian/libarrow-flight400.install -dev/tasks/linux-packages/apache-arrow/debian/libarrow-python-dev.install -dev/tasks/linux-packages/apache-arrow/debian/libarrow-python-flight-dev.install -dev/tasks/linux-packages/apache-arrow/debian/libarrow-python-flight400.install -dev/tasks/linux-packages/apache-arrow/debian/libarrow-python400.install -dev/tasks/linux-packages/apache-arrow/debian/libarrow400.install -dev/tasks/linux-packages/apache-arrow/debian/libgandiva-dev.install -dev/tasks/linux-packages/apache-arrow/debian/libgandiva-glib-dev.install -dev/tasks/linux-packages/apache-arrow/debian/libgandiva-glib-doc.doc-base -dev/tasks/linux-packages/apache-arrow/debian/libgandiva-glib-doc.install -dev/tasks/linux-packages/apache-arrow/debian/libgandiva-glib-doc.links -dev/tasks/linux-packages/apache-arrow/debian/libgandiva-glib400.install -dev/tasks/linux-packages/apache-arrow/debian/libgandiva400.install -dev/tasks/linux-packages/apache-arrow/debian/libparquet-dev.install -dev/tasks/linux-packages/apache-arrow/debian/libparquet-glib-dev.install -dev/tasks/linux-packages/apache-arrow/debian/libparquet-glib-doc.doc-base -dev/tasks/linux-packages/apache-arrow/debian/libparquet-glib-doc.install -dev/tasks/linux-packages/apache-arrow/debian/libparquet-glib-doc.links -dev/tasks/linux-packages/apache-arrow/debian/libparquet-glib400.install -dev/tasks/linux-packages/apache-arrow/debian/libparquet400.install -dev/tasks/linux-packages/apache-arrow/debian/libplasma-dev.install -dev/tasks/linux-packages/apache-arrow/debian/libplasma-glib-dev.install -dev/tasks/linux-packages/apache-arrow/debian/libplasma-glib-doc.doc-base -dev/tasks/linux-packages/apache-arrow/debian/libplasma-glib-doc.install -dev/tasks/linux-packages/apache-arrow/debian/libplasma-glib-doc.links -dev/tasks/linux-packages/apache-arrow/debian/libplasma-glib400.install -dev/tasks/linux-packages/apache-arrow/debian/libplasma400.install -dev/tasks/linux-packages/apache-arrow/debian/patches/series -dev/tasks/linux-packages/apache-arrow/debian/plasma-store-server.install -dev/tasks/linux-packages/apache-arrow/debian/rules -dev/tasks/linux-packages/apache-arrow/debian/source/format -dev/tasks/linux-packages/apache-arrow/debian/watch -dev/tasks/requirements*.txt -dev/tasks/conda-recipes/* pax_global_header MANIFEST.in -__init__.pxd -__init__.py requirements.txt *.html *.sgml