Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions .github/workflows/ruby.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:

- uses: ruby/setup-ruby@v1
with:
ruby-version: 3.3.1
ruby-version: 3.1.2

- name: rubocop
uses: reviewdog/action-rubocop@v2.6.0
Expand Down Expand Up @@ -63,11 +63,11 @@ jobs:
with:
fetch-depth: 0

- name: Set up Ruby 3.3.1
- name: Set up Ruby 3.1.2
id: ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.3.1
ruby-version: 3.1.2
bundler-cache: true

- name: Install required package
Expand All @@ -93,12 +93,22 @@ jobs:
- name: Run specs
run: |
cp config_template.yml config.yml
bundle exec rspec -f j -o tmp/rspec_results.json -f p
bundle exec rspec \
-f j -o tmp/rspec_results.json \
-f h -o tmp/rspec_results.html

- name: Save coverage report
uses: actions/upload-artifact@v4
if: always()
with:
name: coverage-report
path: /home/runner/work/netdef-ci-github-app/netdef-ci-github-app/coverage
if-no-files-found: warn

- name: RSpec Report
uses: SonicGarden/rspec-report-action@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
title: 'Unit tests'
json-path: tmp/rspec_results.json
if: always()
if: always()
1 change: 1 addition & 0 deletions config_template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ auth_signature:
github_apps:
- login:
cert:
repo:

redis:
host:
Expand Down
15 changes: 15 additions & 0 deletions db/migrate/20250416153222_add_ci_job_summary.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: BSD-2-Clause
#
# 20240617121935_create_delayed_jobs.rb
# Part of NetDEF CI System
#
# Copyright (c) 2024 by
# Network Device Education Foundation, Inc. ("NetDEF")
#
# frozen_string_literal: true

class AddCiJobSummary < ActiveRecord::Migration[6.0]
def change
add_column :ci_jobs, :summary, :string
end
end
3 changes: 2 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.2].define(version: 2024_10_14_134659) do
ActiveRecord::Schema[7.2].define(version: 2025_04_16_153222) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"

Expand Down Expand Up @@ -78,6 +78,7 @@
t.integer "parent_stage_id"
t.bigint "stage_id"
t.integer "execution_time"
t.string "summary"
t.index ["check_suite_id"], name: "index_ci_jobs_on_check_suite_id"
t.index ["stage_id"], name: "index_ci_jobs_on_stage_id"
end
Expand Down
12 changes: 11 additions & 1 deletion lib/github/build/summary.rb
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,9 @@ def other_message(name, jobs)
end

def generate_message(name, job)
failures = name.downcase.match?('build') ? build_message(job) : tests_message(job)
failures = tests_message(job)
failures = build_message(job) if name.downcase.match?('build')
failures = checkout_message(job) if name.downcase.match?('source')

"- #{job.name} -> https://ci1.netdef.org/browse/#{job.job_ref}\n#{failures}"
end
Expand All @@ -236,6 +238,14 @@ def tests_message(job)
"\t :no_entry_sign: #{failure.test_suite} #{failure.test_case}\n ```\n#{failure.message}\n```\n"
end

def checkout_message(job)
failures = job.summary

return '' if failures.nil?

"```\n#{failures}\n```\n"
end

def build_message(job)
output = BambooCi::Result.fetch(job.job_ref, expand: 'testResults.failedTests.testResult.errors,artifacts')
entry = output.dig('artifacts', 'artifact')&.find { |elem| elem['name'] == 'ErrorLog' }
Expand Down
2 changes: 1 addition & 1 deletion lib/github/check.rb
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ def authenticate_app
def github_app_by_repo
app =
@config['github_apps'].find do |entry|
entry.key? 'repo' and entry['repo'] == @check_suite.pull_request.repository
entry.key? 'repo' and entry['repo'] == @check_suite&.pull_request&.repository
end

@logger.info("github_app_by_repo: #{app.inspect}")
Expand Down
8 changes: 7 additions & 1 deletion lib/github/update_status.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ def initialize(payload)
@check_suite = @job&.check_suite
@failures = payload['failures'] || []

@summary = ''
@summary = payload['output']['summary'] if payload.key? 'output'

logger_initializer
end

Expand Down Expand Up @@ -94,6 +97,8 @@ def update_status
else
failure
@job.update_execution_time
@job.summary = @summary
@job.save
end

return [200, 'Success'] unless @job.check_suite.pull_request.current_execution? @job.check_suite
Expand Down Expand Up @@ -143,8 +148,9 @@ def fetch_delayed_job(queue)
# The unable2find string must match the phrase defined in the ci-files repository file
# github_checks/hook_api.py method __topotest_title_summary
def failure
@job.failure(@github_check)
return if @job.nil?

@job.failure(@github_check)
return failures_stats if @failures.is_a? Array and !@failures.empty?

CiJobFetchTopotestFailures
Expand Down
4 changes: 4 additions & 0 deletions lib/models/ci_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ class CiJob < ActiveRecord::Base

default_scope -> { order(id: :asc) }, all_queries: true

def checkout_code?
name.match(/checkout/i)
end

def finished?
!%w[queued in_progress].include?(status)
end
Expand Down
1 change: 1 addition & 0 deletions spec/factories/ci_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
status { 0 }
job_ref { Faker::Alphanumeric.alphanumeric(number: 18, min_alpha: 3, min_numeric: 3) }
check_ref { Faker::Alphanumeric.alphanumeric(number: 18, min_alpha: 3, min_numeric: 3) }
summary { Faker::Lorem.sentence }

check_suite
stage { create(:stage, status: status, check_suite: check_suite) }
Expand Down
2 changes: 1 addition & 1 deletion spec/lib/github/build/action_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@
end

it 'must not change' do
expect(check_suite_new.reload.stages.first.status).to eq('queued')
expect(check_suite_new.reload.stages.order(id: :asc).first.status).to eq('queued')
end
end

Expand Down
112 changes: 112 additions & 0 deletions spec/lib/github/build/summary_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -312,4 +312,116 @@
expect(ci_job.reload.stage).not_to be_nil
end
end

context 'when the current stage is cancelled' do
let(:stage) { create(:stage, :cancelled, check_suite: check_suite) }
let(:ci_job) { create(:ci_job, stage: stage, check_suite: check_suite) }

before do
ci_job
end

it 'does not update the stage' do
expect { summary.build_summary }.not_to(change { stage.reload.status })
end
end

context 'when stage does not exists' do
let(:ci_job) { create(:ci_job, stage: nil, check_suite: check_suite) }
let(:stage_configuration) { create(:stage_configuration, bamboo_stage_name: 'D', position: 1) }

let(:current_stage) do
create(:stage, name: 'B', check_suite: check_suite, configuration: create(:stage_configuration, position: 1))
end

let(:job_info) do
[
{
name: ci_job.name,
stage: 'D'
}
]
end

before do
allow(BambooCi::RunningPlan).to receive(:fetch).and_return(job_info)
ci_job
stage_configuration
end

it 'must create a new stage' do
summary.build_summary
expect(Stage.all.pluck(:name)).to include('D')
end
end

context 'when the current stage is not mandatory and fails' do
let(:stage1) { create(:stage, :build, check_suite: check_suite) }
let(:stage2) { create(:stage, check_suite: check_suite) }
let(:ci_job) { create(:ci_job, :failure, stage: stage1, check_suite: check_suite) }
let(:ci_job2) { create(:ci_job, stage: stage2, check_suite: check_suite) }

before do
stage1.configuration.update(mandatory: false, position: 1)
stage2.configuration.update(position: 2)

ci_job
ci_job2
summary.build_summary
end

it 'does not cancel the next stage' do
expect(stage1.reload.status).to eq('failure')
expect(stage2.reload.status).to eq('queued')
end
end

context 'when the current stage is mandatory and succeeds' do
let(:stage1) { create(:stage, :build, check_suite: check_suite) }
let(:stage2) { create(:stage, check_suite: check_suite) }
let(:ci_job) { create(:ci_job, :success, stage: stage1, check_suite: check_suite) }
let(:ci_job2) { create(:ci_job, stage: stage2, check_suite: check_suite) }

before do
stage1.configuration.update(mandatory: true, position: 1)
stage2.configuration.update(position: 2)

ci_job
ci_job2
summary.build_summary
end

it 'marks the next stage as in_progress' do
expect(stage1.reload.status).to eq('success')
expect(stage2.reload.status).to eq('in_progress')
end
end

context 'when has a checkout message' do
let(:stage) { create(:stage, :build, check_suite: check_suite) }
let(:ci_job) do
create(:ci_job, :success, stage: stage, check_suite: check_suite, name: 'Sourcecode', summary: 'HI')
end
let(:message) do
"Sourcecode -> https://ci1.netdef.org/browse/#{ci_job.job_ref}\n```\nHI\n```"
end

it 'must update stage' do
expect(summary.send(:generate_message, 'source', ci_job)).to include(message)
end
end

context 'when has not a checkout message' do
let(:stage) { create(:stage, :build, check_suite: check_suite) }
let(:ci_job) do
create(:ci_job, :success, stage: stage, check_suite: check_suite, name: 'Sourcecode')
end
let(:message) do
"Sourcecode -> https://ci1.netdef.org/browse/#{ci_job.job_ref}\n```\nHI\n```"
end

it 'must update stage' do
expect(summary.send(:generate_message, 'source', ci_job)).not_to include(message)
end
end
end