Class: PotentialBuild

Inherits:
Object
  • Object
show all
Includes:
CMake, Configuration, Lcov, ResultsProcessor, Runners
Defined in:
lib/potentialbuild.rb

Overview

Contains the logic flow for executing builds and parsing results

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Runners

#monitor_thread_state, #read_state_singular, #run_scripts, #run_single_script, #run_with_timeout

Methods included from Lcov

#generate_base_command_line, #generate_filter_command_line, #generate_html_command_line, #lcov

Methods included from ResultsProcessor

#get_win32_filename, #match_type_to_possible_fortran, #parse_error_messages, #parse_gcc_line, #parse_generic_line, #parse_msvc_line, #parse_python_or_latex_line, #process_cmake_results, #process_ctest_results, #process_gcc_results, #process_lcov_results, #process_msvc_results, #process_python_results, #recover_file_case, #relative_path

Methods included from Configuration

#_setup_cc_and_cxx, #establish_base_configuration, #establish_os_characteristics, #establish_windows_characteristics, #find_valid_yaml_files, #get_all_yaml_names, #load_configuration, #load_yaml, #setup_compiler_architecture, #setup_compiler_build_generator, #setup_compiler_description, #setup_compiler_extra_flags, #setup_compiler_num_processors, #setup_compiler_target_arch, #setup_compiler_version, #setup_gcc_style_cc_and_cxx, #setup_single_compiler, #symbolize, #which

Methods included from CMake

#cmake_build, #cmake_remove_git_from_path, #cmake_test

Constructor Details

#initialize(client, token, repository, tag_name, commit_sha, branch_name, author, release_url, release_assets, pull_id, pr_base_repository, pr_base_ref) ⇒ PotentialBuild

rubocop:disable Metrics/ParameterLists



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/potentialbuild.rb', line 36

def initialize(client, token, repository, tag_name, commit_sha, branch_name, author, release_url, release_assets, # rubocop:disable Metrics/ParameterLists
               pull_id, pr_base_repository, pr_base_ref)
  @client = client
  @config = load_configuration(repository, (tag_name.nil? ? commit_sha : tag_name), !release_url.nil?)
  @config.repository_name = github_query(@client) { @client.repo(repository).name }
  @config.repository = repository
  @config.token = token
  @repository = repository
  @tag_name = tag_name
  @commit_sha = commit_sha
  @branch_name = branch_name
  @release_url = release_url
  @release_assets = release_assets
  @author = author

  @buildid = @tag_name || @commit_sha
  @refspec = @tag_name || @branch_name

  @pull_id = pull_id
  @pull_request_base_repository = pr_base_repository
  @pull_request_base_ref = pr_base_ref

  @short_buildid = get_short_form(@tag_name) || @commit_sha[0..9]
  unless @pull_id.nil?
    @buildid = "#{@buildid}-PR#{@pull_id}"
    @short_buildid = "#{@short_buildid}-PR#{@pull_id}"
  end

  @package_locations = nil
  @test_results = nil
  @test_messages = []
  @build_results = SortedSet.new
  @package_results = SortedSet.new
  @dateprefix = nil
  @failure = nil
  @test_run = false
  @build_time = nil
  @test_time = nil
  @install_time = nil
  @package_time = nil
  @coverage_lines = 0
  @coverage_total_lines = 0
  @coverage_functions = 0
  @coverage_total_functions = 0
  @coverage_url = nil
  @asset_url = nil
  @acting_as_baseline = false

  @valgrind_counters_results = nil
  @perf_counters_results = nil
  @file_sizes = nil
end

Instance Attribute Details

#branch_nameObject (readonly)

Returns the value of attribute branch_name



33
34
35
# File 'lib/potentialbuild.rb', line 33

def branch_name
  @branch_name
end

#commit_shaObject (readonly)

Returns the value of attribute commit_sha



33
34
35
# File 'lib/potentialbuild.rb', line 33

def commit_sha
  @commit_sha
end

#failureObject

Returns the value of attribute failure



34
35
36
# File 'lib/potentialbuild.rb', line 34

def failure
  @failure
end

#repositoryObject (readonly)

Returns the value of attribute repository



33
34
35
# File 'lib/potentialbuild.rb', line 33

def repository
  @repository
end

#tag_nameObject (readonly)

Returns the value of attribute tag_name



33
34
35
# File 'lib/potentialbuild.rb', line 33

def tag_name
  @tag_name
end

#test_runObject

Returns the value of attribute test_run



34
35
36
# File 'lib/potentialbuild.rb', line 34

def test_run
  @test_run
end

Instance Method Details

#add_dashes(str) ⇒ Object



239
240
241
# File 'lib/potentialbuild.rb', line 239

def add_dashes(str)
  str.gsub(/([0-9]{3,})([A-Z])/, '\1-\2').gsub(/([A-Z])([0-9]{3,})/, '\1-\2')
end

#build_base_name(compiler) ⇒ Object



120
121
122
# File 'lib/potentialbuild.rb', line 120

def build_base_name(compiler)
  "#{@config.repository_name}-#{@buildid}-#{device_id(compiler)}"
end

#checkout(src_dir) ⇒ Object



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/potentialbuild.rb', line 132

def checkout(src_dir)
  # TODO: update this to be a merge, not just a checkout of the pull request branch
  FileUtils.mkdir_p src_dir

  if @pull_id.nil?
    $logger.info("Checking out branch \"#{@refspec}\"")
    _, _, result = run_scripts(
      @config,
      [
        "cd #{src_dir} && git init",
        "cd #{src_dir} && git pull https://#{@config.token}@github.com/#{@repository} \"#{@refspec}\""
      ]
    )

    success = !@commit_sha.nil? && @commit_sha != '' && result.zero?
    _, _, result = run_scripts(@config, ["cd #{src_dir} && git checkout #{@commit_sha}"]) if success
  else
    $logger.info("Checking out PR \"#{@pull_id}\"")
    _, _, result = run_scripts(
      @config,
      [
        "cd #{src_dir} && git init",
        "cd #{src_dir} && git pull https://#{@config.token}@github.com/#{@pull_request_base_repository} refs/pull/#{@pull_id}/head",
        "cd #{src_dir} && git checkout FETCH_HEAD"
      ]
    )
  end

  result.zero?
end

#clone_regression_repositoryObject



318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
# File 'lib/potentialbuild.rb', line 318

def clone_regression_repository
  regression_dir = this_regression_dir
  FileUtils.mkdir_p regression_dir
  return if @config.regression_repository.nil?

  if !@config.regression_commit_sha.nil? && @config.regression_commit_sha != ''
    refspec = @config.regression_commit_sha
  elsif !@config.regression_branch.nil? && @config.regression_branch != ''
    refspec = @config.regression_branch
  else
    $logger.debug('No regression repository checkout info!?!')
    return
  end
  run_scripts(
    @config,
    [
      "cd #{regression_dir} && git init",
      "cd #{regression_dir} && git fetch https://#{@config.token}@github.com/#{@config.regression_repository} #{refspec}",
      "cd #{regression_dir} && git checkout FETCH_HEAD"
    ]
  )
end

#collect_file_sizes(build_dir: File.absolute_path(this_build_dir)) ⇒ Object



493
494
495
496
497
498
499
500
501
502
503
504
# File 'lib/potentialbuild.rb', line 493

def collect_file_sizes(build_dir: File.absolute_path(this_build_dir))
  results = []
  Dir["#{build_dir}/**/size.*"].each do |file|
    file_name = file.sub(/.*size\./, '')
    $logger.info("Parsing #{file}")
    sizes = parse_file_sizes(file)
    sizes['file_name'] = file_name
    results << sizes
  end

  @file_sizes = results
end

#collect_perf_results(build_dir: File.absolute_path(this_build_dir)) ⇒ Object



506
507
508
509
510
511
512
513
514
515
516
517
518
# File 'lib/potentialbuild.rb', line 506

def collect_perf_results(build_dir: File.absolute_path(this_build_dir))
  results = { 'test_files' => [] }

  Dir["#{build_dir}/**/perf.*"].each do |file|
    perf_counters_test_name = file.sub(/.*perf\./, '')
    $logger.info("Parsing #{file}")
    perf_output = parse_perf(build_dir, file)
    perf_output['test_name'] = perf_counters_test_name
    results['test_files'] << perf_output
  end

  @perf_counters_results = results
end

#collect_valgrind_counters_results(build_dir: File.absolute_path(this_build_dir)) ⇒ Object



520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
# File 'lib/potentialbuild.rb', line 520

def collect_valgrind_counters_results(build_dir: File.absolute_path(this_build_dir))
  results = { 'object_files' => [], 'test_files' => [] }

  Dir["#{build_dir}/**/callgrind.*"].each do |file|
    valgrind_counters_test_name = file.sub(/.*callgrind\./, '')
    callgrind_output = parse_callgrind(build_dir, file)
    object_files = callgrind_output.delete('object_files')
    $logger.info("Object files: #{object_files}")

    results['object_files'].concat(object_files)
    callgrind_output['test_name'] = valgrind_counters_test_name
    results['test_files'] << callgrind_output
  end

  results['object_files'].uniq!

  @valgrind_counters_results = results
end

#compilersObject



93
94
95
# File 'lib/potentialbuild.rb', line 93

def compilers
  @config.compilers
end

#configurationObject



163
164
165
# File 'lib/potentialbuild.rb', line 163

def configuration
  @config
end

#descriptive_stringObject



97
98
99
# File 'lib/potentialbuild.rb', line 97

def descriptive_string
  "#{@commit_sha} #{@branch_name} #{@tag_name} #{@buildid}"
end

#device_id(compiler) ⇒ Object



116
117
118
# File 'lib/potentialbuild.rb', line 116

def device_id(compiler)
  "#{compiler[:architecture]}-#{@config.os}-#{@config.os_release}-#{compiler[:description]}#{device_tag(compiler)}"
end

#device_tag(compiler) ⇒ Object



109
110
111
112
113
114
# File 'lib/potentialbuild.rb', line 109

def device_tag(compiler)
  build_type_tag = ''
  build_type_tag = "-#{compiler[:build_tag]}" unless compiler[:build_tag].nil?
  build_type_tag = "#{build_type_tag}-#{compiler[:build_type]}" if compiler[:build_type] !~ /release/i
  build_type_tag
end

#do_build(compiler, regression_baseline, is_release = false) ⇒ Object



283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/potentialbuild.rb', line 283

def do_build(compiler, regression_baseline, is_release = false)
  src_dir = this_src_dir
  build_dir = this_build_dir
  start_time = Time.now
  checkout_succeeded = checkout src_dir  # TODO: Abort if checkout did not succeed...

  this_device_id = device_id compiler
  args = CMakeBuildArgs.new(compiler[:build_type], this_device_id, is_release)
  cmake_build compiler, src_dir, build_dir, this_regression_dir, regression_baseline, args if checkout_succeeded
  @build_time = 0 if @build_time.nil?
  @build_time += (Time.now - start_time)  # TODO: Should we return true here?

end

#do_coverage(compiler) ⇒ Object



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/potentialbuild.rb', line 167

def do_coverage(compiler)
  return nil unless compiler[:coverage_enabled]

  $logger.info("Beginning coverage calculation phase #{release?}")

  build_dir = this_build_dir
  @coverage_total_lines, @coverage_lines, @coverage_total_functions, @coverage_functions = lcov @config, compiler, build_dir
  return if compiler[:coverage_s3_bucket].nil?

  s3_script = "#{File.dirname(File.dirname(__FILE__))}/send_to_s3.py"

  $logger.info('Beginning upload of coverage results to s3')

  out, = run_scripts(
    @config,
    [
      "#{s3_script} #{compiler[:coverage_s3_bucket]} #{get_full_build_name(compiler)} #{build_dir}/lcov-html coverage"
    ]
  )

  @coverage_url = out
  out
end

#do_test(compiler, regression_baseline) ⇒ Object



297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# File 'lib/potentialbuild.rb', line 297

def do_test(compiler, regression_baseline)
  src_dir = this_src_dir
  build_dir = this_build_dir

  build_succeeded = do_build compiler, regression_baseline

  start_time = Time.now
  if ENV['DECENT_CI_SKIP_TEST']
    $logger.debug('Skipping test, DECENT_CI_SKIP_TEST is set in the environment')
  elsif build_succeeded
    cmake_test compiler, src_dir, build_dir, compiler[:build_type]
  end
  @test_time = 0 if @test_time.nil?  # handle the case where test is called more than once

  @test_time += (Time.now - start_time)
end

#do_upload(compiler) ⇒ Object



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/potentialbuild.rb', line 191

def do_upload(compiler)
  return nil if compiler[:s3_upload].nil?

  $logger.info("Beginning upload phase #{release?} #{!compiler[:s3_upload].nil?}")

  build_dir = this_build_dir

  s3_script = "#{File.dirname(File.dirname(__FILE__))}/send_to_s3.py"

  $logger.info('Beginning upload of build assets to s3')

  out, = run_scripts(
    @config,
    [
      "#{s3_script} #{compiler[:s3_upload_bucket]} #{get_full_build_name(compiler)} #{build_dir}/#{compiler[:s3_upload]} assets"
    ]
  )

  @asset_url = out
  out
end

#get_full_build_name(compiler) ⇒ Object



263
264
265
# File 'lib/potentialbuild.rb', line 263

def get_full_build_name(compiler)
  "#{get_short_form(@config.repository_name)}-#{@short_buildid}-#{compiler[:architecture]}-#{get_short_form(compiler[:description])}#{get_short_form(device_tag(compiler))}"
end

#get_initials(str) ⇒ Object



234
235
236
237
# File 'lib/potentialbuild.rb', line 234

def get_initials(str)
  # extracts just the initials from the string
  str.gsub(/[^A-Z0-9.\-a-z_+]/, '').gsub(/[_\-+]./) { |s| s[1].upcase }.sub(/./, &:upcase).gsub(/[^A-Z0-9.]/, '')
end

#get_short_form(str) ⇒ Object



243
244
245
246
247
248
249
250
251
252
253
# File 'lib/potentialbuild.rb', line 243

def get_short_form(str)
  return nil if str.nil?

  if str.length <= 10 && str =~ /[a-zA-Z]/
    str
  elsif (str =~ /.*[A-Z].*/ && str =~ /.*[a-z].*/) || str =~ /.*_.*/ || str =~ /.*-.*/ || str =~ /.*\+.*/
    add_dashes(get_initials(str))
  else
    str.gsub(/[^a-zA-Z0-9.+_]/, '')
  end
end

#needs_regression_test(compiler) ⇒ Object



314
315
316
# File 'lib/potentialbuild.rb', line 314

def needs_regression_test(compiler)
  (!@config.regression_script.nil? || !@config.regression_repository.nil?) && !ENV['DECENT_CI_SKIP_REGRESSIONS'] && !compiler[:skip_regression]
end

#needs_run(compiler) ⇒ Object



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/potentialbuild.rb', line 213

def needs_run(compiler)
  return true if @test_run

  file_names = []
  begin
    files = github_query(@client) { @client.content @config.results_repository, :path => "#{@config.results_path}/#{this_branch_folder}" }

    files.each do |f|
      file_names << f.name
    end
  rescue Octokit::NotFound # rubocop:disable Lint/SuppressedException
    # repository doesn't have a _posts folder yet
  end

  file_names.each do |f|
    return false if f.end_with? results_file_name(compiler)
  end

  true
end

#next_buildObject



341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
# File 'lib/potentialbuild.rb', line 341

def next_build
  @package_locations = nil
  @test_results = nil
  @test_messages = []
  @build_results = SortedSet.new
  @package_results = SortedSet.new
  @dateprefix = nil
  @failure = nil
  @build_time = nil
  @test_time = nil
  @test_run = false
  @package_time = nil
  @install_time = nil
  @valgrind_counters_results = nil
  @perf_counters_results = nil
  @file_sizes = nil
  @coverage_lines = 0
  @coverage_total_lines = 0
  @coverage_functions = 0
  @coverage_total_functions = 0
  @coverage_url = nil
  @asset_url = nil
  @acting_as_baseline = false
end

#parse_callgrind(build_dir, file) ⇒ Object



397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
# File 'lib/potentialbuild.rb', line 397

def parse_callgrind(build_dir, file)
  object_files = {}
  source_files = {}
  functions = {}
  props = {}

  get_name = lambda do |files, id, name|
    if name.nil? || name == ''
      return_value = files[id]
    elsif id.nil?
      return_value = name
    else
      files[id] = name
      return_value = name
    end
    return_value
  end

  object_file = nil
  source_file = nil
  call_count = nil
  called_object_file = nil
  called_source_file = nil
  called_function_name = nil
  called_functions = {}

  IO.foreach(file) do |line|
    if /^(?<field>[a-z]+): (?<data>.*)/ =~ line
      if field == 'totals'
        totals = data.split
        props['totals'] = totals[0].to_i

        if totals.size == 5
          props['conditional_branches'] = totals[1].to_i
          props['conditional_branches_missses'] = totals[2].to_i
          props['indirect_jumps'] = totals[3].to_i
          props['indirect_jump_misses'] = totals[4].to_i
        end
      else
        props[field] = data
      end
    elsif /^ob=(?<objectfileid>\([0-9]+\))?\s*(?<objectfilename>.*)?/ =~ line
      object_file = get_name.call(object_files, objectfileid, objectfilename)
    elsif /^fl=(?<sourcefileid>\([0-9]+\))?\s*(?<sourcefilename>.*)?/ =~ line
      source_file = get_name.call(source_files, sourcefileid, sourcefilename)
    elsif /^(fe|fi)=(?<sourcefileid>\([0-9]+\))?\s*(?<sourcefilename>.*)?/ =~ line
      get_name.call(source_files, sourcefileid, sourcefilename)
    elsif /^fn=(?<functionid>\([0-9]+\))?\s*(?<functionname>.*)?/ =~ line
      get_name.call(functions, functionid, functionname)
    elsif /^cob=(?<calledobjectfileid>\([0-9]+\))?\s*(?<calledobjectfilename>.*)?/ =~ line
      called_object_file = get_name.call(object_files, calledobjectfileid, calledobjectfilename)
    elsif /^(cfi|cfl)=(?<calledsourcefileid>\([0-9]+\))?\s*(?<calledsourcefilename>.*)?/ =~ line
      called_source_file = get_name.call(source_files, calledsourcefileid, calledsourcefilename)
    elsif /^cfn=(?<calledfunctionid>\([0-9]+\))?\s*(?<calledfunctionname>.*)?/ =~ line
      called_function_name = get_name.call(functions, calledfunctionid, calledfunctionname)
    elsif /^calls=(?<count>[0-9]+)?\s+(?<target_position>[0-9]+)/ =~ line # rubocop:disable Lint/UselessAssignment
      call_count = count
    elsif /^(?<subposition>(((\+|-)?[0-9]+)|\*)) (?<cost>[0-9]+)/ =~ line # rubocop:disable Lint/UselessAssignment
      unless call_count.nil?
        this_object_file = called_object_file.nil? ? object_file : called_object_file
        this_source_file = called_source_file.nil? ? source_file : called_source_file

        called_func_is_nil = called_functions[[this_object_file, this_source_file, called_function_name]].nil?
        called_functions[[this_object_file, this_source_file, called_function_name]] = { 'count' => 0, 'cost' => 0 } if called_func_is_nil

        called_functions[[this_object_file, this_source_file, called_function_name]]['count'] += call_count.to_i
        called_functions[[this_object_file, this_source_file, called_function_name]]['cost'] += cost.to_i

        call_count = nil
        called_object_file = nil
        called_source_file = nil
        called_function_name = nil
      end      # elsif line == "\n"

    end
  end

  props['object_files'] = []

  object_files.each_value do |this_file|
    abs_path = File.absolute_path(this_file, build_dir)
    next unless abs_path.start_with?(File.absolute_path(build_dir)) && File.exist?(abs_path)

    # is in subdir?
    $logger.info("Path: #{abs_path}  build_dir #{build_dir}")
    props['object_files'] << { 'name' => Pathname.new(abs_path).relative_path_from(Pathname.new(build_dir)).to_s, 'size' => File.size(abs_path) }
  end

  most_expensive = called_functions.sort_by { |_, v| v['cost'] }.reverse.slice(0, 50)
  most_called = called_functions.sort_by { |_, v| v['count'] }.reverse.slice(0, 50)

  important_functions = most_expensive.to_h.merge(most_called.to_h).collect { |k, v| { 'object_file' => k[0], 'source_file' => k[1], 'function_name' => k[2] }.merge(v) }

  props.merge('data' => important_functions)
end

#parse_file_sizes(file) ⇒ Object



366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
# File 'lib/potentialbuild.rb', line 366

def parse_file_sizes(file)
  props = {}

  names = nil

  IO.foreach(file) do |line|
    if names.nil?
      names = line.split
    else
      values = line.split

      values.each_index do |index|
        props[names[index]] = values[index]
      end
    end
  end

  props
end

#parse_perf(file) ⇒ Object



386
387
388
389
390
391
392
393
394
395
# File 'lib/potentialbuild.rb', line 386

def parse_perf(file)
  props = {}

  IO.foreach(file) do |line|
    values = line.split(',')
    props[values[2]] = values[0].to_i if values.size > (3) && (values[0] != '<not supported>' && values[0] != '')
  end

  props
end

#post_results(compiler, pending) ⇒ Object



572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
# File 'lib/potentialbuild.rb', line 572

def post_results(compiler, pending)
  @dateprefix = DateTime.now.utc.strftime('%F') if @dateprefix.nil?

  test_results_data = []

  test_results_passed = 0
  test_results_total = 0
  test_results_warning = 0

  test_results_failure_counts = {}

  @test_results&.each do |t|
    test_results_total += 1
    test_results_passed += 1 if t.passed
    test_results_warning += 1 if t.warning

    category_index = t.name.index('.')
    category_name = 'Uncategorized'
    category_name = t.name.slice(0, category_index) unless category_index.nil?

    failure_type = t.passed ? 'Passed' : t.failure_type

    if test_results_failure_counts[category_name].nil?
      category = {}
      category.default = 0
      test_results_failure_counts[category_name] = category
    end

    test_results_failure_counts[category_name][failure_type] += 1

    test_results_data << t.inspect
  end

  build_errors = 0
  build_warnings = 0
  build_results_data = []

  unless @build_results.nil?
    begin
      @build_results.each do |b|
        build_errors += 1 if b.error?
        build_results_data << b.inspect
      end
    rescue
      build_errors += 1
      build_results_data << { 'message' => 'CI Issue: Error occurred when processing build results on this test' }
      $logger.warn('Error in processing build_results, maybe a duplicate build_result...?')
    end
    build_warnings = @build_results.count - build_errors
  end

  package_errors = 0
  package_warnings = 0
  package_results_data = []

  @package_results&.each do |b|
    package_errors += 1 if b.error?
    package_warnings += 1 if b.warning?

    package_results_data << b.inspect
  end

  valgrind_counters_total_time = nil
  valgrind_counters_test_count = 0
  valgrind_counters_total_conditional_branches = nil
  valgrind_counters_total_conditional_branch_misses = nil
  valgrind_counters_total_indirect_jumps = nil
  valgrind_counters_total_indirect_jump_misses = nil

  unless @valgrind_counters_results.nil?
    valgrind_counters_total_time = 0
    valgrind_counters_total_conditional_branches = 0
    valgrind_counters_total_conditional_branch_misses = 0
    valgrind_counters_total_indirect_jumps = 0
    valgrind_counters_total_indirect_jump_misses = 0

    @valgrind_counters_results['test_files'].each do |v|
      valgrind_counters_test_count += 1
      valgrind_counters_total_time += v['totals'] unless v['totals'].nil?
      valgrind_counters_total_conditional_branches += v['conditional_branches'] unless v['conditional_branches'].nil?
      valgrind_counters_total_conditional_branch_misses += v['conditional_branch_misses'] unless v['conditional_branch_misses'].nil?
      valgrind_counters_total_indirect_jumps += v['indirect_jumps'] unless v['indirect_jumps'].nil?
      valgrind_counters_total_indirect_jump_misses += v['indirect_jump_misses'] unless v['indirect_jump_misses'].nil?
    end
  end

  perf_counters = {}

  unless @perf_counters_results.nil?
    $logger.debug("perf counters: #{@perf_counters_results}")
    perf_test_count = 0
    @perf_counters_results['test_files'].each do |v|
      perf_test_count += 1
      v.each do |key, value|
        next if value.is_a? String

        new_key = "perf_total_#{key}"

        perf_counters[new_key] = 0 if perf_counters[new_key].nil?

        $logger.debug("Key: '#{new_key}' value: '#{value}'")
        perf_counters[new_key] += value
      end
    end
    perf_counters['perf_test_count'] = perf_test_count
  end

  package_names_string = nil
  unless @package_locations.nil?
    package_names_string = ''
    @package_locations.each do |location|
      package_names_string += "; #{Pathname.new(location).basename}"
    end
  end

  yaml_data = {
    'title' => build_base_name(compiler),
    'permalink' => "#{build_base_name(compiler)}.html",
    'tags' => 'data',
    'layout' => 'ci_results',
    'date' => DateTime.now.utc.strftime('%F %T'),
    'unhandled_failure' => !@failure.nil?,
    'build_error_count' => build_errors,
    'build_warning_count' => build_warnings,
    'package_error_count' => package_errors,
    'package_warning_count' => package_warnings,
    'test_count' => test_results_total,
    'test_passed_count' => test_results_passed,
    'repository' => @repository,
    'compiler' => compiler[:name],
    'compiler_version' => compiler[:version],
    'architecture' => compiler[:architecture],
    'os' => @config.os,
    'os_release' => @config.os_release,
    'is_release' => release?,
    'release_packaged' => !@package_locations.nil?,
    'packaging_skipped' => compiler[:skip_packaging],
    'package_name' => package_names_string,
    'tag_name' => @tag_name,
    'commit_sha' => @commit_sha,
    'branch_name' => @branch_name,
    'test_run' => !@test_results.nil?,
    'pull_request_issue_id' => @pull_id.to_s,
    'pull_request_base_repository' => @pull_request_base_repository.to_s,
    'pull_request_base_ref' => @pull_request_base_ref.to_s,
    'device_id' => device_id(compiler),
    'pending' => pending,
    'build_time' => @build_time,
    'test_time' => @test_time,
    'package_time' => @package_time,
    'install_time' => @install_time,
    'results_repository' => @config.results_repository.to_s,
    'machine_name' => Socket.gethostname.to_s,
    'machine_ip' => Socket.ip_address_list.find { |ai| ai.ipv4? && !ai.ipv4_loopback? }.ip_address.to_s,
    'test_pass_limit' => @config.test_pass_limit,
    'test_warn_limit' => @config.test_warn_limit,
    'coverage_enabled' => compiler[:coverage_enabled],
    'coverage_pass_limit' => compiler[:coverage_pass_limit],
    'coverage_warn_limit' => compiler[:coverage_warn_limit],
    'coverage_lines' => @coverage_lines,
    'coverage_total_lines' => @coverage_total_lines,
    'coverage_functions' => @coverage_functions,
    'coverage_total_functions' => @coverage_total_functions,
    'coverage_url' => @coverage_url,
    'asset_url' => @asset_url,
    'performance_total_time' => valgrind_counters_total_time,
    'performance_test_count' => valgrind_counters_test_count,
    'valgrind_counters_total_time' => valgrind_counters_total_time,
    'valgrind_counters_test_count' => valgrind_counters_test_count,
    'valgrind_counters_total_conditional_branches' => valgrind_counters_total_conditional_branches,
    'valgrind_counters_total_conditional_branch_misses' => valgrind_counters_total_conditional_branch_misses,
    'valgrind_counters_total_indirect_jumps' => valgrind_counters_total_indirect_jumps,
    'valgrind_counters_total_indirect_jump_misses' => valgrind_counters_total_indirect_jump_misses
  }

  yaml_data.merge!(perf_counters)

  json_data = {
    'build_results' => build_results_data,
    'test_results' => test_results_data,
    'failure' => @failure,
    'package_results' => package_results_data,
    'configuration' => yaml_data,
    'performance_results' => @valgrind_counters_results,
    'perf_performance_results' => @perf_counters_results,
    'file_sizes' => @file_sizes
  }

  json_document =
    <<-YAML
#{yaml_data.to_yaml}
---
#{JSON.pretty_generate(json_data)}
    YAML

  test_failed = false
  if @test_results.nil?
    test_color = 'red'
    test_failed = true
    test_string = 'NA'
  else
    test_percent = if test_results_total.zero?
                     100.0
                   else
                     (test_results_passed.to_f / test_results_total.to_f) * 100.0
                   end

    if test_percent > @config.test_pass_limit
      test_color = 'green'
    elsif test_percent > @config.test_warn_limit
      test_color = 'yellow'
    else
      test_color = 'red'
      test_failed = true
    end
    test_string = "#{test_percent.round(2)}%25"
  end

  test_badge = "<a href='#{@config.results_base_url}/#{build_base_name compiler}.html'>![Test Badge](http://img.shields.io/badge/tests%20passed-#{test_string}-#{test_color}.png)</a>"

  build_failed = false
  if build_errors.positive?
    build_color = 'red'
    build_string = 'failing'
    build_failed = true
  elsif build_warnings.positive?
    build_color = 'yellow'
    build_string = 'warnings'
  else
    build_color = 'green'
    build_string = 'passing'
  end

  build_badge = "<a href='#{@config.results_base_url}/#{build_base_name compiler}.html'>![Build Badge](http://img.shields.io/badge/build%20status-#{build_string}-#{build_color}.png)</a>"

  cov_failed = false
  coverage_badge = ''

  if compiler[:coverage_enabled]
    coverage_percent = if @coverage_total_lines.zero?
                         0
                       else
                         (@coverage_lines.to_f / @coverage_total_lines.to_f) * 100.0
                       end

    if coverage_percent >= compiler[:coverage_pass_limit]
      cov_color = 'green'
    elsif coverage_percent >= compiler[:coverage_warn_limit]
      cov_color = 'yellow'
    else
      cov_color = 'red'
      cov_failed = true
    end
    cov_str = "#{coverage_percent.round(2)}%25"

    coverage_badge = "<a href='#{@config.results_base_url}/#{build_base_name compiler}.html'>![Coverage Badge](http://img.shields.io/badge/coverage%20status-#{cov_str}-#{cov_color}.png)</a>"
  end

  github_status = if pending
                    'pending'
                  elsif build_failed || test_failed || cov_failed || !@failure.nil?
                    'failure'
                  else
                    'success'
                  end

  github_status_message = if pending
                            'Build Pending'
                          elsif build_failed
                            'Build Failed'
                          elsif test_failed
                            "Tests Failed (#{test_results_passed} of #{test_results_total} tests passed, #{test_results_warning} test warnings)"
                          elsif cov_failed
                            'Coverage Too Low'
                          else
                            "OK (#{test_results_passed} of #{test_results_total} tests passed, #{test_results_warning} test warnings)"
                          end

  message_counts = Hash.new(0)
  @test_messages.each { |x| message_counts[x.message] += 1 }

  $logger.debug("Message counts loaded: #{message_counts}")

  message_counts_str = ''
  message_counts.each do |message, count|
    message_counts_str += if count > 1
                            " * #{count} tests had: #{message}\n"
                          else
                            " * 1 test had: #{message}\n"
                          end
  end

  $logger.debug("Message counts string: #{message_counts_str}")

  test_failures_counts_str = ''
  test_results_failure_counts.sort { |a, b| a[0].casecmp(b[0]) }.each do |category, value|
    next if value.size <= 1

    test_failures_counts_str += "\n#{category} Test Summary\n"
    sorted_values = value.sort do |a, b|
      if a[0] == 'Passed'
        -1
      else
        b[0] == 'Passed' ? 1 : a[0].casecmp(b[0])
      end
    end
    sorted_values.each do |failure, count|
      test_failures_counts_str += " * #{failure}: #{count}\n"
    end
  end

  github_document = if @failure.nil?
                      <<-GIT
#{@refspec} (#{@author}) - #{device_id compiler}: #{github_status_message}

#{message_counts_str == '' ? '' : 'Messages:\n'}
#{message_counts_str}
#{test_failures_counts_str == '' ? '' : 'Failures:\n'}
#{test_failures_counts_str}

#{build_badge} #{test_badge} #{coverage_badge}
                      GIT
                    else
                      <<-GIT
<a href='#{@config.results_base_url}/#{build_base_name compiler}.html'>Unhandled Failure</a>
                      GIT
                    end

  if @test_run
    File.open("#{@dateprefix}-#{results_file_name compiler}", 'w+') { |f| f.write(json_document) }
    File.open("#{@dateprefix}-COMMENT-#{results_file_name compiler}", 'w+') { |f| f.write(github_document) }

  else
    begin
      if pending
        $logger.info('Posting pending results file')
        response = github_query(@client) do
          @client.create_contents(
            @config.results_repository,
            "#{@config.results_path}/#{this_branch_folder}/#{@dateprefix}-#{results_file_name compiler}",
            "#{Socket.gethostname}: Commit initial build results file: #{@dateprefix}-#{results_file_name compiler}",
            json_document
          )
        end

        $logger.debug("Results document sha set: #{response.content.sha}")
        @results_document_sha = response.content.sha
      else
        raise 'Error, no prior results document sha set' if @results_document_sha.nil?

        $logger.info("Updating contents with sha #{@results_document_sha}")
        github_query(@client) do
          @client.update_contents(
            @config.results_repository,
            "#{@config.results_path}/#{this_branch_folder}/#{@dateprefix}-#{results_file_name compiler}",
            "#{Socket.gethostname}: Commit final build results file: #{@dateprefix}-#{results_file_name compiler}",
            @results_document_sha,
            json_document
          )
        end
      end
    rescue => e
      $logger.error "Error creating / updating results contents file: #{e}"
      raise e
    end

    if !pending && @config.post_results_comment
      if !@commit_sha.nil? && @repository == @config.repository
        github_query(@client) { @client.create_commit_comment(@config.repository, @commit_sha, github_document) }
      elsif !@pull_id.nil?
        github_query(@client) { @client.add_comment(@config.repository, @pull_id, github_document) }
      end
    end

    if !@commit_sha.nil? && @config.post_results_status
      if @pull_request_base_repository.nil?
        github_query(@client) do
          @client.create_status(
            @config.repository,
            @commit_sha,
            github_status,
            :context => device_id(compiler), :target_url => "#{@config.results_base_url}/#{build_base_name compiler}.html", :description => github_status_message
          )
        end
      else
        github_query(@client) do
          @client.create_status(
            @pull_request_base_repository,
            @commit_sha,
            github_status,
            :context => device_id(compiler), :target_url => "#{@config.results_base_url}/#{build_base_name compiler}.html", :description => github_status_message
          )
        end
      end
    end
  end
end

#pull_request?Boolean

Returns:

  • (Boolean)


105
106
107
# File 'lib/potentialbuild.rb', line 105

def pull_request?
  !@pull_id.nil?
end

#release?Boolean

Returns:

  • (Boolean)


101
102
103
# File 'lib/potentialbuild.rb', line 101

def release?
  !@release_url.nil?
end

#results_file_name(compiler) ⇒ Object



124
125
126
# File 'lib/potentialbuild.rb', line 124

def results_file_name(compiler)
  "#{build_base_name compiler}-results.html"
end

#set_as_baselineObject



89
90
91
# File 'lib/potentialbuild.rb', line 89

def set_as_baseline
  @acting_as_baseline = true
end

#short_build_base_name(compiler) ⇒ Object



128
129
130
# File 'lib/potentialbuild.rb', line 128

def short_build_base_name(compiler)
  "#{@config.repository_name}-#{compiler[:architecture]}-#{@config.os}-#{@buildid}"
end

#this_branch_folderObject



255
256
257
258
259
260
261
# File 'lib/potentialbuild.rb', line 255

def this_branch_folder
  if !@tag_name.nil? && @tag_name != ''
    add_dashes(get_short_form(@tag_name))
  else
    add_dashes(get_short_form(@branch_name))
  end
end

#this_build_dirObject



275
276
277
# File 'lib/potentialbuild.rb', line 275

def this_build_dir
  File.join(this_src_dir, 'build')
end

#this_regression_dirObject



279
280
281
# File 'lib/potentialbuild.rb', line 279

def this_regression_dir
  File.join(Dir.pwd, 'clone_regressions')
end

#this_src_dirObject



267
268
269
270
271
272
273
# File 'lib/potentialbuild.rb', line 267

def this_src_dir
  if @acting_as_baseline
    File.join(Dir.pwd, 'clone_baseline')
  else
    File.join(Dir.pwd, 'clone_branch')
  end
end

#try_to_repost_asset(response, asset_name) ⇒ Object



539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
# File 'lib/potentialbuild.rb', line 539

def try_to_repost_asset(response, asset_name)
  asset_url = nil

  if !response.nil? && response.state == 'new'
    $logger.error("Error uploading asset #{response.url}")
    asset_url = response.url
  end

  if asset_url.nil?
    $logger.error('nil response, attempting to find release url')
    assets = github_query(@client) { @client.release_assets(@release_url) }

    assets.each do |a|
      if a.name == asset_name
        asset_url = a.url
        break
      end
    end
  end

  return if asset_url.nil?

  $logger.error("Found release url in list of assets: #{asset_url}")
  $logger.error("Deleting existing asset_url and trying again #{asset_url}")
  @package_results << CodeMessage.new('CMakeLists.txt', 1, 0, 'warning', "Error attempting to upload release asset, deleting and trying again. #{asset_url}\nDuring attempt #{try_num}")
  begin
    github_query(@client) { @client.delete_release_asset(asset_url) }
  rescue
    $logger.error("Error deleting failed asset, continuing to next try #{e}")
    @package_results << CodeMessage.new('CMakeLists.txt', 1, 0, 'warning', "Error attempting to delete failed release asset upload.\nDuring attempt #{try_num}\nRelease asset #{e}")
  end
end