<commit_before>// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      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.

/**
 * The build cop bot manages issues for unit tests.
 *
 * The input payload should include:
 *  - xunitXML: the base64 encoded xUnit XML log.
 *  - commit: the commit hash the build was for.
 *  - buildURL: URL to link to for a build.
 *  - repo: the repo being tested (e.g. GoogleCloudPlatform/golang-samples).
 */

// eslint-disable-next-line node/no-extraneous-import
import {Application} from 'probot';
// eslint-disable-next-line node/no-extraneous-import
import {LoggerWithTarget} from 'probot/lib/wrap-logger';
// eslint-disable-next-line node/no-extraneous-import
import {GitHubAPI} from 'probot/lib/github';
import xmljs from 'xml-js';
// eslint-disable-next-line node/no-extraneous-import
import Octokit from '@octokit/rest';

const ISSUE_LABEL = 'buildcop: issue';
const FLAKY_LABEL = 'buildcop: flaky';
const QUIET_LABEL = 'buildcop: quiet';
const BUG_LABELS = 'type: bug,priority: p1';

const LABELS_FOR_FLAKY_ISSUE = BUG_LABELS.split(',').concat([
  ISSUE_LABEL,
  FLAKY_LABEL,
]);
const LABELS_FOR_NEW_ISSUE = BUG_LABELS.split(',').concat([ISSUE_LABEL]);

const EVERYTHING_FAILED_TITLE = 'The build failed';

const NEW_ISSUE_MESSAGE = `This test failed!

To configure my behavior, see [the Build Cop Bot documentation](https://github.com/googleapis/repo-automation-bots/tree/master/packages/buildcop).

If I'm commenting on this issue too often, add the \`buildcop: quiet\` label and
I will stop commenting.

---`;

const FLAKY_MESSAGE = `Looks like this issue is flaky. :worried:

I'm going to leave this open and stop commenting.

A human should fix and close this.

---`;

const FLAKY_AGAIN_MESSAGE = `Oops! Looks like this issue is still flaky. It failed again. :grimacing:

I reopened the issue, but a human will need to close it again.

---`;

interface TestCase {
  package?: string;
  testCase?: string;
  passed: boolean;
  log?: string;
}

interface TestResults {
  passes: TestCase[];
  failures: TestCase[];
}

export interface BuildCopPayload {
  repo: string;
  organization: {login: string}; // Filled in by gcf-utils.
  repository: {name: string}; // Filled in by gcf-utils.
  commit: string;
  buildURL: string;

  xunitXML?: string; // Base64 encoded to avoid JSON escaping issues. Fill in to get separate issues for separate tests.
  testsFailed?: boolean; // Whether the entire build failed. Ignored if xunitXML is set.
}

interface PubSubContext {
  readonly event: string;
  github: GitHubAPI;
  log: LoggerWithTarget;
  payload: BuildCopPayload;
}

export function buildcop(app: Application) {
  app.on('pubsub.message', async (context: PubSubContext) => {
    const owner = context.payload.organization?.login;
    const repo = context.payload.repository?.name;
    const commit = context.payload.commit || '[TODO: set commit]';
    const buildURL = context.payload.buildURL || '[TODO: set buildURL]';

    context.log.info(`[${owner}/${repo}] processing ${buildURL}`);

    let results: TestResults;
    if (context.payload.xunitXML) {
      const xml = Buffer.from(context.payload.xunitXML, 'base64').toString();
      results = buildcop.findTestResults(xml);
    } else {
      if (context.payload.testsFailed === undefined) {
        context.log.info(
          `[${owner}/${repo}] No xunitXML and no testsFailed! Skipping.`
        );
        return;
      }
      if (context.payload.testsFailed) {
        results = {passes: [], failures: [{passed: false}]}; // A single failure is used to indicate the whole build failed.
      } else {
        results = {passes: [], failures: []}; // Tests passed.
      }
    }

    context.log.info(
      `[${owner}/${repo}] Found ${results.passes.length} passed tests and ${results.failures.length} failed tests in this result of ${buildURL}`
    );

    try {
      // Get the list of issues once, before opening/closing any of them.
      const options = context.github.issues.listForRepo.endpoint.merge({
        owner,
        repo,
        per_page: 100,<commit_msg>chore(Build Cop): add more logging (#533)<commit_after>// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      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.

/**
 * The build cop bot manages issues for unit tests.
 *
 * The input payload should include:
 *  - xunitXML: the base64 encoded xUnit XML log.
 *  - commit: the commit hash the build was for.
 *  - buildURL: URL to link to for a build.
 *  - repo: the repo being tested (e.g. GoogleCloudPlatform/golang-samples).
 */

// eslint-disable-next-line node/no-extraneous-import
import {Application} from 'probot';
// eslint-disable-next-line node/no-extraneous-import
import {LoggerWithTarget} from 'probot/lib/wrap-logger';
// eslint-disable-next-line node/no-extraneous-import
import {GitHubAPI} from 'probot/lib/github';
import xmljs from 'xml-js';
// eslint-disable-next-line node/no-extraneous-import
import Octokit from '@octokit/rest';

const ISSUE_LABEL = 'buildcop: issue';
const FLAKY_LABEL = 'buildcop: flaky';
const QUIET_LABEL = 'buildcop: quiet';
const BUG_LABELS = 'type: bug,priority: p1';

const LABELS_FOR_FLAKY_ISSUE = BUG_LABELS.split(',').concat([
  ISSUE_LABEL,
  FLAKY_LABEL,
]);
const LABELS_FOR_NEW_ISSUE = BUG_LABELS.split(',').concat([ISSUE_LABEL]);

const EVERYTHING_FAILED_TITLE = 'The build failed';

const NEW_ISSUE_MESSAGE = `This test failed!

To configure my behavior, see [the Build Cop Bot documentation](https://github.com/googleapis/repo-automation-bots/tree/master/packages/buildcop).

If I'm commenting on this issue too often, add the \`buildcop: quiet\` label and
I will stop commenting.

---`;

const FLAKY_MESSAGE = `Looks like this issue is flaky. :worried:

I'm going to leave this open and stop commenting.

A human should fix and close this.

---`;

const FLAKY_AGAIN_MESSAGE = `Oops! Looks like this issue is still flaky. It failed again. :grimacing:

I reopened the issue, but a human will need to close it again.

---`;

interface TestCase {
  package?: string;
  testCase?: string;
  passed: boolean;
  log?: string;
}

interface TestResults {
  passes: TestCase[];
  failures: TestCase[];
}

export interface BuildCopPayload {
  repo: string;
  organization: {login: string}; // Filled in by gcf-utils.
  repository: {name: string}; // Filled in by gcf-utils.
  commit: string;
  buildURL: string;

  xunitXML?: string; // Base64 encoded to avoid JSON escaping issues. Fill in to get separate issues for separate tests.
  testsFailed?: boolean; // Whether the entire build failed. Ignored if xunitXML is set.
}

interface PubSubContext {
  readonly event: string;
  github: GitHubAPI;
  log: LoggerWithTarget;
  payload: BuildCopPayload;
}

export function buildcop(app: Application) {
  app.on('pubsub.message', async (context: PubSubContext) => {
    const owner = context.payload.organization?.login;
    const repo = context.payload.repository?.name;
    const commit = context.payload.commit || '[TODO: set commit]';
    const buildURL = context.payload.buildURL || '[TODO: set buildURL]';

    context.log.info(`[${owner}/${repo}] processing ${buildURL}`);

    let results: TestResults;
    if (context.payload.xunitXML) {
      const xml = Buffer.from(context.payload.xunitXML, 'base64').toString();
      results = buildcop.findTestResults(xml);
    } else {
      if (context.payload.testsFailed === undefined) {
        context.log.info(
          `[${owner}/${repo}] No xunitXML and no testsFailed! Skipping.`
        );
        return;
      }
      if (context.payload.testsFailed) {
        results = {passes: [], failures: [{passed: false}]}; // A single failure is used to indicate the whole build failed.
      } else {
        results = {passes: [], failures: []}; // Tests passed.
      }
    }

    context.log.info(
      `[${owner}/${repo}] Found ${results.passes.length} passed tests and ${results.failures.length} failed tests in this result of ${buildURL}`
    );
    if (results.passes.length > 0) {
      context.log.info(
        `[${owner}/${repo}] example pass: ${results.passes[0].package}: ${results.passes[0].testCase}`
      );
    }
    if (results.failures.length > 0) {
      context.log.info(
        `[${owner}/${repo}] example failure: ${results.failures[0].package}: ${results.failures[0].testCase}`
      );
    }

    try {
      // Get the list of issues once, before opening/closing any of them.
      const options = context.github.issues.listForRepo.endpoint.merge({
        owner,
        repo,
        per_page: 100,
{'fraction_non_alphanumeric': 0.12325459317585302, 'fraction_numerical': 0.004724409448818898, 'mean_word_length': 3.4891611687087654, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 7, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 8}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5543542', 'n_tokens_mistral': 2953, 'n_tokens_neox': 2894, 'n_words': 944}