Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'qa/spec/tools/reliable_report_spec.rb')
-rw-r--r--qa/spec/tools/reliable_report_spec.rb523
1 files changed, 289 insertions, 234 deletions
diff --git a/qa/spec/tools/reliable_report_spec.rb b/qa/spec/tools/reliable_report_spec.rb
index caef8f89bd5..cf5c9dea794 100644
--- a/qa/spec/tools/reliable_report_spec.rb
+++ b/qa/spec/tools/reliable_report_spec.rb
@@ -3,273 +3,328 @@
describe QA::Tools::ReliableReport do
include QA::Support::Helpers::StubEnv
- subject(:run) { described_class.run(range: range, report_in_issue_and_slack: create_issue) }
-
- let(:slack_notifier) { instance_double("Slack::Notifier", post: nil) }
- let(:influx_client) { instance_double("InfluxDB2::Client", create_query_api: query_api) }
- let(:query_api) { instance_double("InfluxDB2::QueryApi") }
-
- let(:slack_channel) { "#quality-reports" }
- let(:range) { 14 }
- let(:issue_url) { "https://gitlab.com/issue/1" }
- let(:time) { "2021-12-07T04:05:25.000000000+00:00" }
-
- let(:runs) do
- values = {
- "name" => "stable spec",
- "status" => "passed",
- "file_path" => "some/spec.rb",
- "stage" => "manage",
- "_time" => time
- }
- [
- instance_double(
- "InfluxDB2::FluxTable",
- records: [
- instance_double("InfluxDB2::FluxRecord", values: values),
- instance_double("InfluxDB2::FluxRecord", values: values),
- instance_double("InfluxDB2::FluxRecord", values: values.merge({ "_time" => Time.now.to_s }))
- ]
- )
- ]
- end
-
- let(:reliable_runs) do
- values = {
- "name" => "unstable spec",
- "status" => "failed",
- "file_path" => "some/spec.rb",
- "stage" => "create",
- "failure_exception" => "failure message",
- "job_url" => "https://job/url",
- "_time" => time
- }
- [
- instance_double(
- "InfluxDB2::FluxTable",
- records: [
- instance_double("InfluxDB2::FluxRecord", values: { **values, "status" => "passed" }),
- instance_double("InfluxDB2::FluxRecord", values: values),
- instance_double("InfluxDB2::FluxRecord", values: values.merge({ "_time" => Time.now.to_s }))
- ]
- )
- ]
- end
-
- def flux_query(reliable:)
- <<~QUERY
- from(bucket: "e2e-test-stats-main")
- |> range(start: -#{range}d)
- |> filter(fn: (r) => r._measurement == "test-stats")
- |> filter(fn: (r) => r.run_type == "staging-full" or
- r.run_type == "staging-sanity" or
- r.run_type == "production-full" or
- r.run_type == "production-sanity" or
- r.run_type == "package-and-qa" or
- r.run_type == "nightly"
- )
- |> filter(fn: (r) => r.job_name != "airgapped" and
- r.job_name != "instance-image-slow-network" and
- r.job_name != "nplus1-instance-image"
- )
- |> filter(fn: (r) => r.status != "pending" and
- r.merge_request == "false" and
- r.quarantined == "false" and
- r.smoke == "false" and
- r.reliable == "#{reliable}"
- )
- |> filter(fn: (r) => r["_field"] == "job_url" or
- r["_field"] == "failure_exception" or
- r["_field"] == "id"
+ describe '.run' do
+ subject(:run) { described_class.run(range: range, report_in_issue_and_slack: create_issue) }
+
+ let(:slack_notifier) { instance_double("Slack::Notifier", post: nil) }
+ let(:influx_client) { instance_double("InfluxDB2::Client", create_query_api: query_api) }
+ let(:query_api) { instance_double("InfluxDB2::QueryApi") }
+
+ let(:slack_channel) { "#quality-reports" }
+ let(:range) { 14 }
+ let(:issue_url) { "https://gitlab.com/issue/1" }
+ let(:time) { "2021-12-07T04:05:25.000000000+00:00" }
+ let(:failure_message) { 'random failure message' }
+
+ let(:runs) do
+ values = {
+ "name" => "stable spec",
+ "status" => "passed",
+ "file_path" => "some/spec.rb",
+ "stage" => "manage",
+ "_time" => time
+ }
+ [
+ instance_double(
+ "InfluxDB2::FluxTable",
+ records: [
+ instance_double("InfluxDB2::FluxRecord", values: values),
+ instance_double("InfluxDB2::FluxRecord", values: values),
+ instance_double("InfluxDB2::FluxRecord", values: values.merge({ "_time" => Time.now.to_s }))
+ ]
)
- |> pivot(rowKey: ["_time"], columnKey: ["_field"], valueColumn: "_value")
- |> group(columns: ["name"])
- QUERY
- end
-
- def markdown_section(summary, result, stage, type)
- <<~SECTION.strip
- #{summary_table(summary, type, true)}
-
- ## #{stage} (1)
-
- <details>
- <summary>Executions table</summary>
-
- #{table(result, ['NAME', 'RUNS', 'FAILURES', 'FAILURE RATE'], "Top #{type} specs in '#{stage}' stage for past #{range} days", true)}
-
- </details>
- SECTION
- end
-
- def summary_table(summary, type, markdown = false)
- table(summary, %w[STAGE COUNT], "#{type.capitalize} spec summary for past #{range} days".ljust(50), markdown)
- end
-
- def table(rows, headings, title, markdown = false)
- Terminal::Table.new(
- headings: headings,
- title: markdown ? nil : title,
- rows: rows,
- style: markdown ? { border: :markdown } : { all_separators: true }
- )
- end
-
- def name_column(spec_name, exceptions_and_job_urls = {})
- "**name**: #{spec_name}<br>**file**: spec.rb#{exceptions_markdown(exceptions_and_job_urls)}"
- end
+ ]
+ end
- def exceptions_markdown(exceptions_and_job_urls)
- exceptions_and_job_urls.empty? ? '' : "<br>**Exceptions**:<br>- [`failure message`](https://job/url)"
- end
+ let(:reliable_runs) do
+ values = {
+ "name" => "unstable spec",
+ "status" => "failed",
+ "file_path" => "some/spec.rb",
+ "stage" => "create",
+ "failure_exception" => failure_message,
+ "job_url" => "https://job/url",
+ "_time" => time
+ }
+ [
+ instance_double(
+ "InfluxDB2::FluxTable",
+ records: [
+ instance_double("InfluxDB2::FluxRecord", values: { **values, "status" => "passed" }),
+ instance_double("InfluxDB2::FluxRecord", values: values),
+ instance_double("InfluxDB2::FluxRecord", values: values.merge({ "_time" => Time.now.to_s }))
+ ]
+ )
+ ]
+ end
- before do
- stub_env("QA_INFLUXDB_URL", "url")
- stub_env("QA_INFLUXDB_TOKEN", "token")
- stub_env("SLACK_WEBHOOK", "slack_url")
- stub_env("CI_API_V4_URL", "gitlab_api_url")
- stub_env("GITLAB_ACCESS_TOKEN", "gitlab_token")
+ def flux_query(reliable:)
+ <<~QUERY
+ from(bucket: "e2e-test-stats-main")
+ |> range(start: -#{range}d)
+ |> filter(fn: (r) => r._measurement == "test-stats")
+ |> filter(fn: (r) => r.run_type == "staging-full" or
+ r.run_type == "staging-sanity" or
+ r.run_type == "production-full" or
+ r.run_type == "production-sanity" or
+ r.run_type == "package-and-qa" or
+ r.run_type == "nightly"
+ )
+ |> filter(fn: (r) => r.job_name != "airgapped" and
+ r.job_name != "instance-image-slow-network" and
+ r.job_name != "nplus1-instance-image"
+ )
+ |> filter(fn: (r) => r.status != "pending" and
+ r.merge_request == "false" and
+ r.quarantined == "false" and
+ r.smoke == "false" and
+ r.reliable == "#{reliable}"
+ )
+ |> filter(fn: (r) => r["_field"] == "job_url" or
+ r["_field"] == "failure_exception" or
+ r["_field"] == "id"
+ )
+ |> pivot(rowKey: ["_time"], columnKey: ["_field"], valueColumn: "_value")
+ |> group(columns: ["name"])
+ QUERY
+ end
- allow(RestClient::Request).to receive(:execute)
- allow(Slack::Notifier).to receive(:new).and_return(slack_notifier)
- allow(InfluxDB2::Client).to receive(:new).and_return(influx_client)
+ def markdown_section(summary, result, stage, type)
+ <<~SECTION.strip
+ #{summary_table(summary, type, true)}
- allow(query_api).to receive(:query).with(query: flux_query(reliable: false)).and_return(runs)
- allow(query_api).to receive(:query).with(query: flux_query(reliable: true)).and_return(reliable_runs)
- end
+ ## #{stage} (1)
- context "without report creation" do
- let(:create_issue) { "false" }
+ <details>
+ <summary>Executions table</summary>
- it "does not create report issue", :aggregate_failures do
- expect { run }.to output.to_stdout
+ #{table(result, ['NAME', 'RUNS', 'FAILURES', 'FAILURE RATE'], "Top #{type} specs in '#{stage}' stage for past #{range} days", true)}
- expect(RestClient::Request).not_to have_received(:execute)
- expect(slack_notifier).not_to have_received(:post)
+ </details>
+ SECTION
end
- end
- context "with report creation" do
- let(:create_issue) { "true" }
- let(:iid) { 2 }
- let(:old_iid) { 1 }
- let(:issue_endpoint) { "gitlab_api_url/projects/278964/issues" }
-
- let(:common_api_args) do
- {
- verify_ssl: false,
- headers: { "PRIVATE-TOKEN" => "gitlab_token" },
- cookies: {}
- }
+ def summary_table(summary, type, markdown = false)
+ table(summary, %w[STAGE COUNT], "#{type.capitalize} spec summary for past #{range} days".ljust(50), markdown)
end
- let(:create_issue_response) do
- instance_double(
- "RestClient::Response",
- code: 200,
- body: { web_url: issue_url, iid: iid }.to_json
+ def table(rows, headings, title, markdown = false)
+ Terminal::Table.new(
+ headings: headings,
+ title: markdown ? nil : title,
+ rows: rows,
+ style: markdown ? { border: :markdown } : { all_separators: true }
)
end
- let(:open_issues_response) do
- instance_double(
- "RestClient::Response",
- code: 200,
- body: [{ web_url: issue_url, iid: iid }, { web_url: issue_url, iid: old_iid }].to_json
- )
+ def name_column(spec_name, exceptions_and_job_urls = {})
+ "**Name**: #{spec_name}<br>**File**: [spec.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/qa/qa/specs/features/some/spec.rb)#{exceptions_markdown(exceptions_and_job_urls)}"
end
- let(:success_response) do
- instance_double("RestClient::Response", code: 200, body: {}.to_json)
+ def exceptions_markdown(exceptions_and_job_urls)
+ exceptions_and_job_urls.empty? ? '' : "<br>**Exceptions**:<br>- [`#{failure_message}`](https://job/url)"
end
- let(:issue_body) do
- <<~TXT.strip
- [[_TOC_]]
-
- # Candidates for promotion to reliable (#{Date.today - range} - #{Date.today})
+ before do
+ stub_env("QA_INFLUXDB_URL", "url")
+ stub_env("QA_INFLUXDB_TOKEN", "token")
+ stub_env("SLACK_WEBHOOK", "slack_url")
+ stub_env("CI_API_V4_URL", "gitlab_api_url")
+ stub_env("GITLAB_ACCESS_TOKEN", "gitlab_token")
+
+ allow(RestClient::Request).to receive(:execute)
+ allow(Slack::Notifier).to receive(:new).and_return(slack_notifier)
+ allow(InfluxDB2::Client).to receive(:new).and_return(influx_client)
+
+ allow(query_api).to receive(:query).with(query: flux_query(reliable: false)).and_return(runs)
+ allow(query_api).to receive(:query).with(query: flux_query(reliable: true)).and_return(reliable_runs)
+ end
- Total amount: **1**
+ context "without report creation" do
+ let(:create_issue) { "false" }
- #{markdown_section([['manage', 1]], [[name_column('stable spec'), 3, 0, '0%']], 'manage', 'stable')}
+ it "does not create report issue", :aggregate_failures do
+ expect { run }.to output.to_stdout
- # Reliable specs with failures (#{Date.today - range} - #{Date.today})
+ expect(RestClient::Request).not_to have_received(:execute)
+ expect(slack_notifier).not_to have_received(:post)
+ end
+ end
- Total amount: **1**
+ context "with report creation" do
+ let(:create_issue) { "true" }
+ let(:iid) { 2 }
+ let(:old_iid) { 1 }
+ let(:issue_endpoint) { "gitlab_api_url/projects/278964/issues" }
+
+ let(:common_api_args) do
+ {
+ verify_ssl: false,
+ headers: { "PRIVATE-TOKEN" => "gitlab_token" },
+ cookies: {}
+ }
+ end
+
+ let(:create_issue_response) do
+ instance_double(
+ "RestClient::Response",
+ code: 200,
+ body: { web_url: issue_url, iid: iid }.to_json
+ )
+ end
- #{markdown_section([['create', 1]], [[name_column('unstable spec', { 'failure message' => 'https://job/url' }), 3, 2, '66.67%']], 'create', 'unstable')}
- TXT
+ let(:open_issues_response) do
+ instance_double(
+ "RestClient::Response",
+ code: 200,
+ body: [{ web_url: issue_url, iid: iid }, { web_url: issue_url, iid: old_iid }].to_json
+ )
+ end
+
+ let(:success_response) do
+ instance_double("RestClient::Response", code: 200, body: {}.to_json)
+ end
+
+ before do
+ allow(RestClient::Request).to receive(:execute).exactly(4).times.and_return(
+ create_issue_response,
+ open_issues_response,
+ success_response,
+ success_response
+ )
+ end
+
+ shared_examples 'report creation' do
+ it "creates report issue" do
+ expect { run }.to output.to_stdout
+
+ expect(RestClient::Request).to have_received(:execute).with(
+ method: :post,
+ url: issue_endpoint,
+ payload: {
+ title: "Reliable e2e test report",
+ description: expected_issue_body,
+ labels: "reliable test report,Quality,test,type::maintenance,automation:ml"
+ },
+ **common_api_args
+ )
+ expect(RestClient::Request).to have_received(:execute).with(
+ method: :get,
+ url: "#{issue_endpoint}?labels=reliable test report&state=opened",
+ **common_api_args
+ )
+ expect(RestClient::Request).to have_received(:execute).with(
+ method: :put,
+ url: "#{issue_endpoint}/#{old_iid}",
+ payload: {
+ state_event: "close"
+ },
+ **common_api_args
+ )
+ expect(RestClient::Request).to have_received(:execute).with(
+ method: :post,
+ url: "#{issue_endpoint}/#{old_iid}/notes",
+ payload: {
+ body: "Closed issue in favor of ##{iid}"
+ },
+ **common_api_args
+ )
+ expect(slack_notifier).to have_received(:post).with(
+ icon_emoji: ":tanuki-protect:",
+ text: expected_slack_text
+ )
+ end
+ end
+
+ context "with disallowed exception" do
+ let(:failure_message) { 'random failure message' }
+
+ let(:expected_issue_body) do
+ <<~TXT.strip
+ [[_TOC_]]
+
+ # Candidates for promotion to reliable (#{Date.today - range} - #{Date.today})
+
+ Total amount: **1**
+
+ #{markdown_section([['manage', 1]], [[name_column('stable spec'), 3, 0, '0%']], 'manage', 'stable')}
+
+ # Reliable specs with failures (#{Date.today - range} - #{Date.today})
+
+ Total amount: **1**
+
+ #{markdown_section([['create', 1]], [[name_column('unstable spec', { failure_message => 'https://job/url' }), 3, 2, '66.67%']], 'create', 'unstable')}
+ TXT
+ end
+
+ let(:expected_slack_text) do
+ <<~TEXT
+ ```#{summary_table([['manage', 1]], 'stable')}```
+ ```#{summary_table([['create', 1]], 'unstable')}```
+
+ #{issue_url}
+ TEXT
+ end
+
+ it_behaves_like "report creation"
+ end
+
+ context "with allowed exception" do
+ let(:failure_message) { 'Ambiguous match' }
+
+ let(:expected_issue_body) do
+ <<~TXT.strip
+ [[_TOC_]]
+
+ # Candidates for promotion to reliable (#{Date.today - range} - #{Date.today})
+
+ Total amount: **1**
+
+ #{markdown_section([['manage', 1]], [[name_column('stable spec'), 3, 0, '0%']], 'manage', 'stable')}
+ TXT
+ end
+
+ let(:expected_slack_text) do
+ <<~TEXT
+ ```#{summary_table([['manage', 1]], 'stable')}```
+ ```#{summary_table([], 'unstable')}```
+
+ #{issue_url}
+ TEXT
+ end
+
+ it_behaves_like "report creation"
+ end
end
- before do
- allow(RestClient::Request).to receive(:execute).exactly(4).times.and_return(
- create_issue_response,
- open_issues_response,
- success_response,
- success_response
- )
- end
+ context "with failure" do
+ let(:create_issue) { "true" }
- it "creates report issue" do
- expect { run }.to output.to_stdout
-
- expect(RestClient::Request).to have_received(:execute).with(
- method: :post,
- url: issue_endpoint,
- payload: {
- title: "Reliable e2e test report",
- description: issue_body,
- labels: "reliable test report,Quality,test,type::maintenance,automation:ml"
- },
- **common_api_args
- )
- expect(RestClient::Request).to have_received(:execute).with(
- method: :get,
- url: "#{issue_endpoint}?labels=reliable test report&state=opened",
- **common_api_args
- )
- expect(RestClient::Request).to have_received(:execute).with(
- method: :put,
- url: "#{issue_endpoint}/#{old_iid}",
- payload: {
- state_event: "close"
- },
- **common_api_args
- )
- expect(RestClient::Request).to have_received(:execute).with(
- method: :post,
- url: "#{issue_endpoint}/#{old_iid}/notes",
- payload: {
- body: "Closed issue in favor of ##{iid}"
- },
- **common_api_args
- )
- expect(slack_notifier).to have_received(:post).with(
- icon_emoji: ":tanuki-protect:",
- text: <<~TEXT
- ```#{summary_table([['manage', 1]], 'stable')}```
- ```#{summary_table([['create', 1]], 'unstable')}```
-
- #{issue_url}
- TEXT
- )
+ before do
+ allow(query_api).to receive(:query).and_raise("Connection error!")
+ end
+
+ it "notifies failure", :aggregate_failures do
+ expect { expect { run }.to raise_error("Connection error!") }.to output.to_stdout
+
+ expect(slack_notifier).to have_received(:post).with(
+ icon_emoji: ":sadpanda:",
+ text: "Reliable reporter failed to create report. Error: ```Connection error!```"
+ )
+ end
end
end
- context "with failure" do
- let(:create_issue) { "true" }
+ describe "#allowed_failure?" do
+ subject(:reliable_report) { described_class.new(14) }
- before do
- allow(query_api).to receive(:query).and_raise("Connection error!")
+ it "returns true for an allowed failure" do
+ expect(reliable_report.send(:allowed_failure?, "Couldn't find option named abc")).to be true
end
- it "notifies failure", :aggregate_failures do
- expect { expect { run }.to raise_error("Connection error!") }.to output.to_stdout
-
- expect(slack_notifier).to have_received(:post).with(
- icon_emoji: ":sadpanda:",
- text: "Reliable reporter failed to create report. Error: ```Connection error!```"
- )
+ it "returns false for disallowed failure" do
+ expect(reliable_report.send(:allowed_failure?,
+ %q([Unable to find css "[data-testid=\"user_action_dropdown\"]"]))).to be false
end
end
end