Class: GitlabQuality::TestTooling::TestMeta::TestMetaUpdater

Inherits:
Object
  • Object
show all
Includes:
Concerns::FindSetDri
Defined in:
lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Concerns::FindSetDri

#test_dri

Constructor Details

#initialize(token:, project:, specs_file:, processor:, ref: 'master', dry_run: false) ⇒ TestMetaUpdater

Returns a new instance of TestMetaUpdater.



13
14
15
16
17
18
19
20
21
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 13

def initialize(token:, project:, specs_file:, processor:, ref: 'master', dry_run: false)
  @specs_file = specs_file
  @token = token
  @project = project
  @ref = ref
  @dry_run = dry_run
  @processor = processor
  @processed_commits = {}
end

Instance Attribute Details

#dry_runObject (readonly)

Returns the value of attribute dry_run.



11
12
13
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 11

def dry_run
  @dry_run
end

#processed_commitsObject (readonly)

Returns the value of attribute processed_commits.



11
12
13
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 11

def processed_commits
  @processed_commits
end

#processorObject (readonly)

Returns the value of attribute processor.



11
12
13
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 11

def processor
  @processor
end

#projectObject (readonly)

Returns the value of attribute project.



11
12
13
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 11

def project
  @project
end

#refObject (readonly)

Returns the value of attribute ref.



11
12
13
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 11

def ref
  @ref
end

#report_issueObject (readonly)

Returns the value of attribute report_issue.



11
12
13
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 11

def report_issue
  @report_issue
end

#specs_fileObject (readonly)

Returns the value of attribute specs_file.



11
12
13
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 11

def specs_file
  @specs_file
end

#tokenObject (readonly)

Returns the value of attribute token.



11
12
13
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 11

def token
  @token
end

Instance Method Details

#add_processed_commit(file_path, changed_line_no, branch, spec) ⇒ Hash<String,Hash>

Add processed commits.

processed_commits has the following form. Note that each key in the :commits hash is the changed line number and the value is the spec changed.

{

"/file/path/for/spec_1" =>
  { :commits =>
    {
      "34" => {"stage"=> "create", "product_group" => "source_code".. },
      "38" => {"stage"=> "create", "product_group" => "source_code".. }
    },
    :branch => #<Gitlab::ObjectifiedHash>
  },
"/file/path/for/spec_2" =>
  { :commits =>
    {
      "34" => {"stage"=> "create", "product_group" => "source_code".. },
      "38" => {"stage"=> "create", "product_group" => "source_code".. }
    },
    :branch => #<Gitlab::ObjectifiedHash>
  },

}

Parameters:

  • file_path (<String>)

    the file path to the spec

  • changed_line_no (<Integer>)

    the changed line number for the commit

  • branch (<Gitlab::ObjectifiedHash>)

    the branch for the commit

  • spec (<Hash>)

    spec details hash

Returns:

  • (Hash<String,Hash>)

    processed_commits



74
75
76
77
78
79
80
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 74

def add_processed_commit(file_path, changed_line_no, branch, spec)
  if processed_commits[file_path].nil?
    processed_commits[file_path] = { commits: { changed_line_no.to_s => spec }, branch: branch }
  elsif processed_commits[file_path][:commits][changed_line_no.to_s].nil?
    processed_commits[file_path][:commits].merge!({ changed_line_no.to_s => spec })
  end
end

#batch_limitInteger

Returns the number of records to process

Returns:

  • (Integer)


41
42
43
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 41

def batch_limit
  ENV.fetch('BATCH_LIMIT', 10).to_i
end

#branch_for_file_path(file_path) ⇒ <Gitlab::ObjectifiedHash>

Returns the branch for the given file_path

Parameters:

  • file_path (String)

    path to the file

Returns:

  • (<Gitlab::ObjectifiedHash>)


95
96
97
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 95

def branch_for_file_path(file_path)
  processed_commits[file_path] && processed_commits[file_path][:branch]
end

#commit_changes(branch, message, file_path, new_content) ⇒ Gitlab::ObjectifiedHash

Commit changes to a branch

Parameters:

  • branch (Gitlab::ObjectifiedHash)

    the branch to commit to

  • message (String)

    the message to commit

  • new_content (String)

    the new content to commit

Returns:

  • (Gitlab::ObjectifiedHash)

    the commit



191
192
193
194
195
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 191

def commit_changes(branch, message, file_path, new_content)
  @commits_client ||= (dry_run ? GitlabClient::CommitsDryClient : GitlabClient::CommitsClient)
                        .new(token: token, project: project)
  @commits_client.create(branch['name'], file_path, new_content, message)
end

#commit_processed?(file_path, changed_line_no) ⇒ Boolean

Checks if changes have already been made to given file_path and line number

Parameters:

  • file_path (String)

    path to the file

  • changed_line_no (Integer)

    updated line number

Returns:

  • (Boolean)


87
88
89
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 87

def commit_processed?(file_path, changed_line_no)
  processed_commits[file_path] && processed_commits[file_path][:commits][changed_line_no.to_s]
end

#create_branch(name_prefix, name, ref) ⇒ Gitlab::ObjectifiedHash

Create a branch from the ref

Parameters:

  • name_prefix (String)

    the prefix to attach to the branch name

  • name (String)

    the branch name

Returns:

  • (Gitlab::ObjectifiedHash)

    the new branch



179
180
181
182
183
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 179

def create_branch(name_prefix, name, ref)
  branch_name = [name_prefix, name.gsub(/\W/, '-')]
  @branches_client ||= (dry_run ? GitlabClient::BranchesDryClient : GitlabClient::BranchesClient).new(token: token, project: project)
  @branches_client.create(branch_name.join('-'), ref)
end

#create_merge_request(title, branch, assignee_id = nil, reviewer_ids = [], labels = '') ⇒ Gitlab::ObjectifiedHash

Create a Merge Request with a given branch

Parameters:

  • title_prefix (String)

    the prefix of the title

  • example_name (String)

    the example

  • branch (Gitlab::ObjectifiedHash)

    the branch

  • assignee_id (Integer) (defaults to: nil)
  • reviewer_ids (Array<Integer>) (defaults to: [])
  • labels (String) (defaults to: '')

    comma seperated list of labels

Returns:

  • (Gitlab::ObjectifiedHash)

    the created merge request



206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 206

def create_merge_request(title, branch, assignee_id = nil, reviewer_ids = [], labels = '')
  description = yield

  merge_request_client.create_merge_request(
    title: title,
    source_branch: branch['name'],
    target_branch: ref,
    description: description,
    labels: labels,
    assignee_id: assignee_id,
    reviewer_ids: reviewer_ids)
end

#existing_merge_requests(title:) ⇒ Array<Gitlab::ObjectifiedHash>

Returns and existing merge request with the given title

Parameters:

  • title: (String)

    Title of the merge request

Returns:

  • (Array<Gitlab::ObjectifiedHash>)

    Merge requests



325
326
327
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 325

def existing_merge_requests(title:)
  merge_request_client.find(options: { search: title, in: 'title', state: 'opened' })
end

#fetch_dri_id(test, devops_stage, section) ⇒ Array<Integer, String>

Fetch the id for the dri of the product group and stage The first item returned is the id of the assignee and the second item is the handle

Parameters:

  • test (Hash)

    object

  • devops_stage (String)
  • section (String)

Returns:

  • (Array<Integer, String>)


274
275
276
277
278
279
280
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 274

def fetch_dri_id(test, devops_stage, section)
  product_group = determine_product_group(test)
  return unless product_group

  assignee_handle = ENV.fetch('QA_TEST_DRI_HANDLE', nil) || test_dri(product_group, devops_stage, section)
  [user_id_for_username(assignee_handle), assignee_handle]
end

#fetch_issue(iid:) ⇒ Gitlab::ObjectifiedHash

Fetch an issue

Parameters:

  • iid: (String)

    The iid of the issue

Returns:

  • (Gitlab::ObjectifiedHash)


240
241
242
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 240

def fetch_issue(iid:)
  issue_client.find_issues(iid: iid).first
end

#find_example_match_lines(content, example_name) ⇒ Array<String, Integer>

Find all lines that contain any part of the example name

Parameters:

  • content (String)

    the content of the spec file

  • example_name (String)

    the name of example to find

Returns:

  • (Array<String, Integer>)

    first value holds the matched line, the second value holds the line number of matched line



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 114

def find_example_match_lines(content, example_name)
  lines = content.split("\n")

  matched_lines = []
  example_name_for_parsing = example_name.dup

  lines.each_with_index do |line, line_index|
    string_within_quotes = spec_desc_string_within_quotes(line)

    regex = /^\s?#{Regexp.escape(string_within_quotes)}/ if string_within_quotes

    if !example_name_for_parsing.empty? && regex && example_name_for_parsing.match(regex)
      example_name_for_parsing.sub!(regex, '')
      matched_lines << [line, line_index]
    end
  rescue StandardError => e
    puts "Error: #{e}"
  end

  matched_lines
end

#get_file_contents(file_path:, branch:) ⇒ String

Fetch contents of file from the repository

Parameters:

  • file_path (String)

    path to the file

  • branch (String)

    branch ref

Returns:

  • (String)

    contents of the file



104
105
106
107
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 104

def get_file_contents(file_path:, branch:)
  repository_files = GitlabClient::RepositoryFilesClient.new(token: token, project: project, file_path: file_path, ref: branch || ref)
  repository_files.file_contents
end

#indentation(line) ⇒ Object

Provide indentation based on the given line

@param line the line to use for indentation @return indentation



312
313
314
315
316
317
318
319
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 312

def indentation(line)
  # Indent the same number of spaces as the current line
  no_of_spaces = line[/\A */].size
  # If the first char on current line is not a quote, add two more spaces
  no_of_spaces += /['"]/.match?(line.lstrip[0]) ? 0 : 2

  " " * no_of_spaces
end

#invoke!Object



23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 23

def invoke!
  JSON.parse(File.read(specs_file)).tap do |contents|
    @report_issue = contents['report_issue']

    contents['specs'].each do |spec|
      processor.create_commit(spec, self)
      break if processed_commits.keys.count >= batch_limit
    end

    processor.create_merge_requests(self)

    processor.post_process(self)
  end
end

#issue_clientGitlabIssueDryClient | GitlabIssueClient

Returns the GitlabIssueClient or GitlabIssueDryClient based on the value of dry_run

Returns:

  • (GitlabIssueDryClient | GitlabIssueClient)


370
371
372
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 370

def issue_client
  @issue_client ||= (dry_run ? GitlabClient::IssuesDryClient : GitlabClient::IssuesClient).new(token: token, project: project)
end

#issue_is_closed?(issue) ⇒ Boolean

Check if issue is closed

Parameters:

  • issue (Gitlab::ObjectifiedHash)

    the issue

Returns:

  • (Boolean)

    True or False



223
224
225
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 223

def issue_is_closed?(issue)
  issue['state'] == 'closed'
end

#issue_scoped_label(issue, scope) ⇒ String

Get scoped label from issue

Parameters:

  • issue (Gitlab::ObjectifiedHash)

    the issue

  • scope (String)

Returns:

  • (String)

    scoped label



232
233
234
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 232

def issue_scoped_label(issue, scope)
  issue['labels'].detect { |label| label.match(/#{scope}::/) }
end

#label_from_feature_category(feature_category) ⇒ String

Infers the group label from the provided feature category

Parameters:

  • feature_category (String)

    feature category

Returns:

  • (String)


343
344
345
346
347
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 343

def label_from_feature_category(feature_category)
  labels = labels_inference.infer_labels_from_feature_category(feature_category)
  group_label = labels.find { |label| label.start_with?('group::') }
  group_label ? %(/label ~"#{group_label}") : ''
end

#label_from_product_group(product_group) ⇒ String

Infers product group label from the provided product group

Parameters:

  • product_group (String)

    product group

Returns:

  • (String)


333
334
335
336
337
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 333

def label_from_product_group(product_group)
  label = labels_inference.infer_labels_from_product_group(product_group).to_a.first

  label ? %(/label ~"#{label}") : ''
end

#labels_inferenceObject

Returns a cached instance of GitlabQuality::TestTooling::LabelsInference

@return [GitlabQuality::TestTooling::LabelsInference]


387
388
389
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 387

def labels_inference
  @labels_inference ||= GitlabQuality::TestTooling::LabelsInference.new
end

#merge_request_clientMergeRequestDryClient | MergeRequest

Returns the MergeRequestDryClient or MergeRequest based on the value of dry_run

Returns:

  • (MergeRequestDryClient | MergeRequest)


377
378
379
380
381
382
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 377

def merge_request_client
  @merge_request_client ||= (dry_run ? GitlabClient::MergeRequestsDryClient : GitlabClient::MergeRequestsClient).new(
    token: token,
    project: project
  )
end

#post_message_on_slack(message) ⇒ HTTP::Response

Post a message on Slack

Parameters:

  • message (String)

    the message to post

Returns:

  • (HTTP::Response)


294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 294

def post_message_on_slack(message)
  channel = Runtime::Env.slack_alerts_channel
  slack_options = {
    slack_webhook_url: ENV.fetch('CI_SLACK_WEBHOOK_URL', nil),
    channel: channel,
    username: "GitLab Quality Test Tooling",
    icon_emoji: ':warning:',
    message: message
  }
  puts "Posting Slack message to channel: #{channel}"

  (dry_run ? GitlabQuality::TestTooling::Slack::PostToSlackDry : GitlabQuality::TestTooling::Slack::PostToSlack).new(**slack_options).invoke!
end

#post_note_on_issue(note, issue_url) ⇒ Gitlab::ObjectifiedHash

Post note on isse

Parameters:

  • note (String)

    the note to post

Returns:

  • (Gitlab::ObjectifiedHash)


248
249
250
251
252
253
254
255
256
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 248

def post_note_on_issue(note, issue_url)
  iid = issue_url&.split('/')&.last # split url segment, last segment of path is the issue id
  if iid
    issue_client.create_issue_note(iid: iid, note: note)
  else
    Runtime::Logger.info("#{self.class.name}##{__method__} Note was NOT posted on issue: #{issue_url}")
    nil
  end
end

#post_note_on_merge_request(note, merge_request_iid) ⇒ Gitlab::ObjectifiedHash

Post a note of merge reqest

Parameters:

  • note (String)
  • merge_request_iid (Integer)

Returns:

  • (Gitlab::ObjectifiedHash)


263
264
265
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 263

def post_note_on_merge_request(note, merge_request_iid)
  merge_request_client.create_note(note: note, merge_request_iid: merge_request_iid)
end

#quarantined?(matched_lines, file_contents) ⇒ Bolean

Scans the content from the matched line until ‘do` is found to look for quarantine token

Parameters:

  • matched_lines (Array)

    an array of arrays containing the matched line and their index

  • file_contents (String)

    the content of the spec file

Returns:

  • (Bolean)


141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 141

def quarantined?(matched_lines, file_contents)
  lines = file_contents.split("\n")

  matched_lines.each do |matched_line|
    matched_line_starting_index = matched_line[1]

    lines[matched_line_starting_index..].each do |line|
      return true if line.include?('quarantine: {')
      break if / do$/.match?(line)
    end
  end

  false
end

Returns the link to the Grafana dashboard for single spec metrics

Parameters:

  • example_name (String)

    the full example name

Returns:

  • (String)


353
354
355
356
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 353

def single_spec_metrics_link(example_name)
  base_url = "https://dashboards.quality.gitlab.net/d/cW0UMgv7k/single-spec-metrics?orgId=1&var-run_type=All&var-name="
  base_url + CGI.escape(example_name)
end

#spec_desc_string_within_quotes(line) ⇒ String

Returns any test description string within single or double quotes

Parameters:

  • line (String)

    the line to check for any quoted string

Returns:

  • (String)

    the match or nil if no match



362
363
364
365
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 362

def spec_desc_string_within_quotes(line)
  match = line.match(/(?:it|describe|context|\s)+ ['"]([^'"]*)['"]/)
  match ? match[1] : nil
end

#update_matched_line(matched_line, content) ⇒ Array<String, Integer>

Update the provided matched_line with content from the block if given

Parameters:

  • matched_line (Array<String, Integer>)

    first value holds the line content, the second value holds the line number

  • content (String)

    full orignal content of the spec file

Returns:

  • (Array<String, Integer>)

    first value holds the new content, the second value holds the line number of the test



161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 161

def update_matched_line(matched_line, content)
  lines = content.split("\n")

  begin
    resulting_line = block_given? ? yield(matched_line[0]) : matched_line[0]
    lines[matched_line[1]] = resulting_line
  rescue StandardError => e
    puts "Error: #{e}"
  end

  [lines.join("\n") << "\n", matched_line[1]]
end

#user_id_for_username(username) ⇒ Integer

Fetch id for the given GitLab username/handle

Parameters:

  • username (String)

Returns:

  • (Integer)


286
287
288
# File 'lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb', line 286

def user_id_for_username(username)
  issue_client.find_user_id(username: username)
end