Repository: venmo/slather Branch: master Commit: 4dba0321f81f Files: 95 Total size: 348.6 KB Directory structure: gitextract_cr8xe6x0/ ├── .coveralls.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── assets/ │ ├── highlight.pack.js │ └── slather.css ├── bin/ │ └── slather ├── lib/ │ ├── cocoapods_plugin.rb │ ├── slather/ │ │ ├── command/ │ │ │ ├── coverage_command.rb │ │ │ ├── setup_command.rb │ │ │ └── version_command.rb │ │ ├── coverage_file.rb │ │ ├── coverage_info.rb │ │ ├── coverage_service/ │ │ │ ├── cobertura_xml_output.rb │ │ │ ├── coveralls.rb │ │ │ ├── gutter_json_output.rb │ │ │ ├── hardcover.rb │ │ │ ├── html_output.rb │ │ │ ├── json_output.rb │ │ │ ├── llvm_cov_output.rb │ │ │ ├── simple_output.rb │ │ │ └── sonarqube_xml_output.rb │ │ ├── coveralls_coverage.rb │ │ ├── profdata_coverage_file.rb │ │ ├── project.rb │ │ └── version.rb │ └── slather.rb ├── slather.gemspec └── spec/ ├── fixtures/ │ ├── FixtureFramework/ │ │ ├── FixtureFramework.h │ │ ├── FlashExperiment.swift │ │ └── Info.plist │ ├── FixtureFrameworkTests/ │ │ ├── FixtureFrameworkTests.swift │ │ ├── FlashExperimentTests.swift │ │ └── Info.plist │ ├── fixtures/ │ │ ├── Fixtures.swift │ │ ├── Supporting Files/ │ │ │ └── fixtures-Prefix.pch │ │ ├── fixtures.h │ │ ├── fixtures.m │ │ ├── fixtures_cpp.cpp │ │ ├── fixtures_cpp.h │ │ ├── fixtures_m.h │ │ ├── fixtures_m.m │ │ ├── fixtures_mm.h │ │ ├── fixtures_mm.mm │ │ ├── more_files/ │ │ │ ├── Branches.h │ │ │ ├── Branches.m │ │ │ ├── Empty.h │ │ │ ├── Empty.m │ │ │ ├── peekaview.h │ │ │ └── peekaview.m │ │ └── other_fixtures.m │ ├── fixtures.xcodeproj/ │ │ ├── project.pbxproj │ │ ├── project.xcworkspace/ │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── xcschemes/ │ │ ├── aggregateFixturesTests.xcscheme │ │ ├── fixtures.xcscheme │ │ ├── fixturesTests.xcscheme │ │ ├── fixturesTestsSecond.xcscheme │ │ └── fixturesTwo.xcscheme │ ├── fixtures.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ ├── IDEWorkspaceChecks.plist │ │ └── xcschemes/ │ │ └── fixturesTestsWorkspace.xcscheme │ ├── fixturesTests/ │ │ ├── BranchesTests.m │ │ ├── Supporting Files/ │ │ │ ├── en.lproj/ │ │ │ │ └── InfoPlist.strings │ │ │ └── fixturesTests-Info.plist │ │ ├── fixturesTests.m │ │ ├── fixturesTestsSecond.m │ │ └── peekaviewTests💣.m │ ├── fixturesTwo/ │ │ ├── fixturesTwo.h │ │ └── fixturesTwo.m │ ├── fixtures_html/ │ │ ├── Branches.m.html │ │ ├── BranchesTests.m.html │ │ ├── fixtures.m.html │ │ ├── fixturesTests.m.html │ │ ├── index.html │ │ └── peekaviewTests💣.m.html │ ├── gutter.json │ └── sonarqube-generic-coverage.xml ├── slather/ │ ├── cocoapods_plugin_spec.rb │ ├── coverage_file_spec.rb │ ├── coverage_service/ │ │ ├── cobertura_xml_spec.rb │ │ ├── coveralls_spec.rb │ │ ├── gutter_json_spec.rb │ │ ├── hardcover_spec.rb │ │ ├── html_output_spec.rb │ │ ├── json_spec.rb │ │ ├── llvm_cov_spec.rb │ │ ├── simple_output_spec.rb │ │ └── sonarqube_xml_spec.rb │ ├── profdata_coverage_spec.rb │ └── project_spec.rb └── spec_helper.rb ================================================ FILE CONTENTS ================================================ ================================================ FILE: .coveralls.yml ================================================ service_name: travis-ci ================================================ FILE: .gitignore ================================================ *.gem *.rbc .bundle .config .yardoc Gemfile.lock InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp *.bundle *.so *.o *.a mkmf.log .vendor .ruby-version cobertura.xml .gutter.json html *.gcda *.gcno # Xcode # *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata *.xccheckout *.moved-aside DerivedData *.hmap *.ipa *.xcuserstate *.DS_Store # VSCode IDE .vscode/ .rdbgrc* # python .venv/ # JetBrains IDE .idea/ # Test output report.llcov report.json # VSCode .vscode .rdbgrc* ================================================ FILE: .travis.yml ================================================ language: objective-c script: bundle exec rake osx_image: xcode12.2 cache: bundler before_install: - curl http://curl.haxx.se/ca/cacert.pem -o /usr/local/share/cacert.pem - gem install bundler -v "~> 2.0" --no-document install: - bundle install --without=documentation ================================================ FILE: CHANGELOG.md ================================================ # CHANGELOG ## v2.8.5 * Update xcodeproj to 1.27.1 to support Xcode 16 folder references [authiatr](https://github.com/authiatr) [#574](https://github.com/SlatherOrg/slather/pull/574) ## v2.8.4 * Don't crash when 0% coverage is causing empty JSON [jarrodlombardo-EventBase](https://github.com/jarrodlombardo-EventBase) [#570](https://github.com/SlatherOrg/slather/pull/570) * Xcode 16 compatibility (as of beta 5) [ksuther](https://github.com/ksuther) [#568](https://github.com/SlatherOrg/slather/pull/568) ## v2.8.3 * Fix coverage_file.source_file_pathname [alfredofernandes](https://github.com/alfredofernandes) [#565](https://github.com/SlatherOrg/slather/pull/565) * update cobertura DTD to a working URL. [jarrodlombardo-EventBase](https://github.com/jarrodlombardo-EventBase) [#564](https://github.com/SlatherOrg/slather/pull/564) ## v2.8.2 * coverage_info.include_files? needs a default true return value for when source_files is empty. [jarrodlombardo-EventBase](https://github.com/jarrodlombardo-EventBase) [#563](https://github.com/SlatherOrg/slather/pull/563) ## v2.8.1 * cobertura.sourceforge.net should use https instead of http [jarrodlombardo-EventBase](https://github.com/jarrodlombardo-EventBase) [#559](https://github.com/SlatherOrg/slather/pull/559) * Handle include globs [dnedrow](https://github.com/dnedrow) [#553](https://github.com/SlatherOrg/slather/pull/553) ## v2.8.0 * Add `--ymlfile` option to override `.slather.yml` [jarrodlombardo-EventBase](https://github.com/jarrodlombardo-EventBase) [#550](https://github.com/SlatherOrg/slather/pull/550) * Update list.js [AndriiZakhliupanyi](https://github.com/AndriiZakhliupanyi) [#546](https://github.com/SlatherOrg/slather/pull/546) ## v2.7.5 * Add `--cdn-assets` flag [sushant-here](https://github.com/sushant-here) [#537](https://github.com/SlatherOrg/slather/pull/537) * Update nokogiri version ## v2.7.4 * Support Ruby 3.2.0 [crazymanish](https://github.com/crazymanish) [#532](https://github.com/SlatherOrg/slather/pull/532) ## v2.7.3 * Support Coveralls parallel runs [paulz](https://github.com/paulz) [#523](https://github.com/SlatherOrg/slather/pull/523) * Update nokogiri version [anil291987](https://github.com/anil291987) [#518](https://github.com/SlatherOrg/slather/pull/518) [#524](https://github.com/SlatherOrg/slather/pull/524) ## v2.7.2 * Update xcodeproj version [adamyanalunas](https://github.com/adamyanalunas) [#502](https://github.com/SlatherOrg/slather/pull/502) * Update nokogiri version [jwelton](https://github.com/jwelton) [#503](https://github.com/SlatherOrg/slather/pull/503) * Support alternate CI systems in coveralls output [fermoyadrop](https://github.com/fermoyadrop) [#504](https://github.com/SlatherOrg/slather/pull/504) * Add Bitrise support to coveralls output [fermoyadrop](https://github.com/fermoyadrop) [#504](https://github.com/SlatherOrg/slather/pull/505) ## v2.7.1 * Support generating coverage for framework targets [onato](https://github.com/onato) [#482](https://github.com/SlatherOrg/slather/pull/482) * Show number of lines in HTML report [SiemianHS](https://github.com/SiemianHS) [#494](https://github.com/SlatherOrg/slather/pull/494) * Fixed issues with HTML report generation [fchiba](https://github.com/fchiba) [#483](https://github.com/SlatherOrg/slather/pull/483) [#484](https://github.com/SlatherOrg/slather/pull/484) * Don't fail if a source file doesn't exist [chillpop](https://github.com/chillpop) [#492](https://github.com/SlatherOrg/slather/pull/492) ## v2.7.0 * Add Branch Coverage data for ProfData coverage files [hborawski](https://github.com/hborawski) [#477](https://github.com/SlatherOrg/slather/pull/477) * Fixed 'Argument list too long' when running 'xcrun llvm-cov' [samuelsainz](https://github.com/samuelsainz) [#476](https://github.com/SlatherOrg/slather/pull/476) ## v2.6.1 * Update nokogiri to 1.11 [ashin-omg](https://github.com/ashin-omg) [#473](https://github.com/SlatherOrg/slather/pull/473) ## v2.6.0 * Added GitHub actions support [martin-key](https://github.com/martin-key), [troyfontaine](https://github.com/troyfontaine) [#468](https://github.com/SlatherOrg/slather/pull/468) ## v2.5.0 * Fixed activesupport and cocoapods dependencies [daneov](https://github.com/daneov) [#456](https://github.com/SlatherOrg/slather/pull/467) * Fixed typo in documentation [descorp](https://github.com/descorp) [#456](https://github.com/SlatherOrg/slather/pull/463) ## v2.4.9 * Added support for Sonarqube output [adellibovi](https://github.com/adellibovi) [#456](https://github.com/SlatherOrg/slather/pull/456) ## v2.4.8 * Optimize performance for many binaries [cltnschlosser](https://github.com/cltnschlosser) [#455](https://github.com/SlatherOrg/slather/pull/455) * Don't generate line 0 in profdata_coverage_file.rb from line with error [tthbalazs](https://github.com/tthbalazs) [#449](https://github.com/SlatherOrg/slather/pull/449) * coveralls dependency update [GRiMe2D](https://github.com/GRiMe2D) [#448](https://github.com/SlatherOrg/slather/pull/448) ## v2.4.7 * Update dependencies [dnedrow](https://github.com/dnedrow) * Fixed errors when llvm-cov argument length exceeds ARG_MAX [weibel](https://github.com/weibel) [#414](https://github.com/SlatherOrg/slather/pull/414) * Show "No coverage directory found." instead of "implicit conversion nil into String" [phimage](https://github.com/phimage) [#381](https://github.com/SlatherOrg/slather/pull/381) [#341](https://github.com/SlatherOrg/slather/issues/341) ## v2.4.6 * Fix .dSYM and .swiftmodule files filtering in find_binary_files() [krin-san](https://github.com/krin-san) [#368](https://github.com/SlatherOrg/slather/pull/368) * Fixed loading coverage for a single source file [blackm00n](https://github.com/blackm00n) [#377](https://github.com/SlatherOrg/slather/pull/377) [#398](https://github.com/SlatherOrg/slather/pull/398) * Fixed truncated file list in HTML export [miroslavkovac](https://github.com/miroslavkovac) [#402](https://github.com/SlatherOrg/slather/pull/402) [#261](https://github.com/SlatherOrg/slather/issues/261) ## v2.4.5 * Support for specifying a specific binary architecture [ksuther](https://github.com/ksuther), [nickolas-pohilets](https://github.com/nickolas-pohilets) [#367](https://github.com/SlatherOrg/slather/pull/367) * Added absolute statement count to simple output (instead of showing just a percentage) [barrault01](https://github.com/barrault01), [ivanbrunel](https://github.com/ivanbruel) [#365](https://github.com/SlatherOrg/slather/pull/365) * Updated nokogiri dependency version [#363](https://github.com/SlatherOrg/slather/issues/363), [#366](https://github.com/SlatherOrg/slather/pull/366) * slather now requires ruby 2.1 or later (10.13 ships with 2.3.3) ## v2.4.4 * Added llvm-cov output format [sgtsquiggs](https://github.com/sgtsquiggs) [#354](https://github.com/SlatherOrg/slather/pull/354) * Exclude swiftmodule from product search [lampietti](https://github.com/lampietti) [#352](https://github.com/SlatherOrg/slather/pull/352) ## v2.4.3 * Initial Xcode 9 support [ksuther](https://github.com/ksuther) [#339](https://github.com/SlatherOrg/slather/pull/339), [ivanbrunel](https://github.com/ivanbruel) [#321](https://github.com/SlatherOrg/slather/pull/321), [FDREAL](https://github.com/FDREAL) [#338](https://github.com/SlatherOrg/slather/pull/338) * Add `--json` output option for basic JSON format not specific to any particular service. [ileitch](https://github.com/ileitch) [#318](https://github.com/SlatherOrg/slather/pull/318) ## v2.4.2 * Restored support for Xcode 7 [ButkiewiczP](https://github.com/ButkiewiczP) [#304](https://github.com/slatherOrg/slather/pull/308) * Added Jenkins Pipeline support for Coveralls [daneov](https://github.com/daneov) [#304](https://github.com/slatherOrg/slather/pull/304) ## v2.4.1 * Add `--configuration` option [thasegaw](https://github.com/thasegaw) [#294](https://github.com/slatherOrg/slather/pull/294) * Fix misdetection of Xcode version if Spotlight hasn't indexed Xcode yet [ksuther](https://github.com/ksuther) [#298](https://github.com/slatherOrg/slather/pull/298) * Better verbose message when no binaries are found [ksuther](https://github.com/ksuther) [#300](https://github.com/slatherOrg/slather/pull/300) ## v2.4.0 * Xcode 8.3 support. [ksuther](https://github.com/ksuther) [#291](https://github.com/SlatherOrg/slather/pull/291) * Automatically ignore headers in Xcode platform SDKs. [ksuther](https://github.com/ksuther) [#286](https://github.com/SlatherOrg/slather/pull/286) * Automatically handle schemes with multiple build or test targets [serges147](https://github.com/serges147) [#275](https://github.com/SlatherOrg/slather/pull/275) * Added TeamCity as a CI service option [joshrlesch](https://github.com/joshrlesch) [#279](https://github.com/SlatherOrg/slather/pull/279) * Handle UTF-8 characters correctly in HTML reports [0xced](https://github.com/0xced) [#259](https://github.com/SlatherOrg/slather/pull/259) * Fix hanging `xcodebuild` invocation when getting derived data path. [arthurtoper](https://github.com/arthurtoper) [#238](https://github.com/SlatherOrg/slather/pull/238), [#197](https://github.com/SlatherOrg/slather/issues/197), [#212](https://github.com/SlatherOrg/slather/issues/212), [#234](https://github.com/SlatherOrg/slather/issues/234) ## v2.3.0 * Fixes broken fallback value of `input_format` inside `configure_input_format` [sbhklr](https://github.com/sbhklr) [#233](https://github.com/SlatherOrg/slather/pull/233), [#232](https://github.com/SlatherOrg/slather/issues/232) * Add `--travispro` flag [sbhklr](https://github.com/sbhklr) [#223](https://github.com/SlatherOrg/slather/pull/223), [#219](https://github.com/SlatherOrg/slather/issues/219) * Fixes silent failure in case of unsuccessful upload to Coveralls [sbhklr](https://github.com/sbhklr) [#222](https://github.com/SlatherOrg/slather/pull/222), [#217](https://github.com/SlatherOrg/slather/issues/217) ## v2.2.1 * Make `project.coverage_files` public * Add docs attribute reader to `project.rb` [bootstraponline](https://github.com/bootstraponline) [#209](https://github.com/SlatherOrg/slather/pull/209) * Add `--decimals` flag [bootstraponline](https://github.com/bootstraponline) [#207](https://github.com/SlatherOrg/slather/pull/207) * Add `slather version` command [bootstraponline](https://github.com/bootstraponline) [#208](https://github.com/SlatherOrg/slather/pull/208) ## v2.2.0 * Fix nil crash in `project.rb` derived_data_path [bootstraponline](https://github.com/bootstraponline) [#203](https://github.com/SlatherOrg/slather/pull/203) * Fix for correct line number for lines that are hit thousands or millions of time in llvm-cov. [Mihai Parv](https://github.com/mihaiparv) [#202](https://github.com/SlatherOrg/slather/pull/202), [#196](https://github.com/SlatherOrg/slather/issues/196) * Generate coverate for multiple binaries by passing multiple `--binary-basename` or `--binary-file` arguments, or by using an array in `.slather.yml` [Kent Sutherland](https://github.com/ksuther) [#188](https://github.com/SlatherOrg/slather/pull/188) * Support for specifying source file patterns using the `--source-files` option or the source_files key in `.slather.yml` [Matej Bukovinski](https://github.com/matej) [#201](https://github.com/SlatherOrg/slather/pull/201) * Improve getting schemes. Looks for user scheme in case no shared scheme is found. [Matyas Hlavacek](https://github.com/matyashlavacek) [#182](https://github.com/SlatherOrg/slather/issues/182) * Search Xcode workspaces for schemes. Workspaces are checked if no matching scheme is found in the project. [Kent Sutherland](https://github.com/ksuther) [#193](https://github.com/SlatherOrg/slather/pull/193), [#191](https://github.com/SlatherOrg/slather/issues/191) * Fix for hit counts in thousands or millions being output as floats intead of integers [Carl Hill-Popper](https://github.com/chillpop) [#190](https://github.com/SlatherOrg/slather/pull/190) ## v2.1.0 * Support for Xcode workspaces. Define `workspace` configuration in `.slather.yml` or use the `--workspace` argument if you build in a workspace. * Improved slather error messages [Kent Sutherland](https://github.com/ksuther) [#178](https://github.com/SlatherOrg/slather/issues/178) * Re-add Teamcity support [Boris Bügling](https://github.com/neonichu) [#180](https://github.com/SlatherOrg/slather/pull/180) * Show lines that are hit thousands or millions of time in llvm-cov [Kent Sutherland](https://github.com/ksuther) [#179](https://github.com/SlatherOrg/slather/pull/179) * Fix for setting scheme/workspace from configuration file. [Boris Bügling](https://github.com/neonichu) [#183](https://github.com/SlatherOrg/slather/pull/183) ## v2.0.2 * Escape the link to file names properly [Thomas Mellenthin](https://github.com/melle) [#158](https://github.com/SlatherOrg/slather/pull/158) * Product info is now read from schemes. Specify a scheme in `.slather.yml` or with the `--scheme` argument to ensure consistent results. Automatically detect the derived data directory from `xcodebuild` [Kent Sutherland](https://github.com/ksuther) [#174](https://github.com/SlatherOrg/slather/pull/174) * Xcode 7.3 compatibility (updated path returned by `profdata_coverage_dir`) [Kent Sutherland](https://github.com/ksuther) [#125](https://github.com/SlatherOrg/slather/issues/125), [#169](https://github.com/SlatherOrg/slather/pull/169) * Improve matching of xctest bundles when using `--binary-basename` [Kent Sutherland](https://github.com/ksuther) [#167](https://github.com/SlatherOrg/slather/pull/167) * Build Statistic Reporting for TeamCity [Michael Myers](https://github.com/michaelmyers) [#150](https://github.com/SlatherOrg/slather/pull/150) * Use named classes for subcommands in bin/slather [bootstraponline](https://github.com/bootstraponline) [#170](https://github.com/SlatherOrg/slather/pull/170) ## v2.0.1 * Fixes how `profdata_coverage_dir` is created. [guidomb](https://github.com/guidomb) [#145](https://github.com/SlatherOrg/slather/pull/145) ## v2.0.0 * Correct html rendering when using profdata format [cutz](https://github.com/cutz) [#124](https://github.com/SlatherOrg/slather/pull/124) * Making HTML directory self contained [Colin Cornaby](https://github.com/colincornaby) [#137](https://github.com/SlatherOrg/slather/pull/137) * Add `binary_basename` configuration option [Boris Bügling](https://github.com/neonichu) [#128](https://github.com/SlatherOrg/slather/pull/128) * Add support to profdata file format [Simone Civetta](https://github.com/viteinfinite) [Olivier Halligon](https://github.com/AliSoftware) [Matt Delves](https://github.com/mattdelves) [Pierre-Marc Airoldi](https://github.com/petester42) [#92](https://github.com/venmo/slather/pull/92) ## v1.8.3 * Add buildkite support to coveralls [David Hardiman](https://github.com/dhardiman) [#98](https://github.com/venmo/slather/pull/98) * Update to xcodeproj 0.28.0 to avoid collisions with Cocoapods 0.39.0 [Julian Krumow](https://github.com/tarbrain) [#106](https://github.com/venmo/slather/pull/106)/[#109](https://github.com/venmo/slather/pull/109) ## v1.8.1 * Fixed dependency conflict with CocoaPods v0.38 * Updated usage of cocoapods plugin API since it has changed in v0.38 [Julian Krumow](https://github.com/tarbrain) [#95](https://github.com/venmo/slather/pull/95) ## v1.7.0 * Objective-C++ support [ben-ng](https://github.com/ben-ng) [#63](https://github.com/venmo/slather/pull/63) ## v1.6.0 * Add CircleCI support [Jonathan Hersh](https://github.com/jhersh) [#55](https://github.com/venmo/slather/pull/55) ## v1.5.4 * Fix calculation of branch coverage when a class has no branches [Julian Krumow](https://github.com/tarbrain) [#40](https://github.com/venmo/slather/pull/40) * Always consider empty files as 100% tested [Boris Bügling](https://github.com/neonichu) [#45](https://github.com/venmo/slather/pull/45) ## v1.5.2 * Add an option to define the output directory for cobertura xml reports [Julian Krumow](https://github.com/tarbrain) [#37](https://github.com/venmo/slather/pull/37) ## v1.5.1 * Avoid crashes when coverage data is empty * Fix bug which prevented source files without coverage data to be included in Cobertura xml report [Julian Krumow](https://github.com/tarbrain) [#34](https://github.com/venmo/slather/pull/34) ## v1.5.0 * Add support for Cobertura [Julian Krumow](https://github.com/tarbrain) [#30](https://github.com/venmo/slather/pull/30) ## v1.4.0 * Implement a CocoaPods plugin [Kyle Fuller](https://github.com/kylef) [#25](https://github.com/venmo/slather/pull/25) * Avoid getting 'Infinity' or 'NaN' when dividing by 0.0 [Mark Larsen](https://github.com/marklarr) * Ignore exceptions about files not existing by using 'force' [Mark Larsen](https://github.com/marklarr) ## v1.3.0 * Add Gutter JSON output [Boris Bügling](https://github.com/neonichu) [#24](https://github.com/venmo/slather/pull/24) ## v1.2.1 * Fix typo --simple-output description [Ayaka Nonaka](https://github.com/ayanonagon) [#19](https://github.com/venmo/slather/pull/19) * Remove broken travis pro support [Mark Larsen](https://github.com/marklarr) [#22](https://github.com/venmo/slather/pull/22) * Fix exception for files without `@interface` or `@implementation` [Piet Brauer](https://github.com/pietbrauer) [#23](https://github.com/venmo/slather/pull/23) ## v1.2.0 * Remove duplicate coverage files, favoring the file with higher coverage. [Ayaka Nonaka](https://github.com/ayanonagon) [#16](https://github.com/venmo/slather/pull/16) * Add support for access token and Travis Pro [Chris Maddern](https://github.com/chrismaddern) [#17](https://github.com/venmo/slather/pull/17) ## v1.1.0 * Support for code coverage of pods [Mark Larsen](https://github.com/marklarr) ## v1.0.1 * Fix coverage search for files that contain spaces [Mark Larsen](https://github.com/marklarr) ## v1.0.0 * beautified README [Ayaka Nonaka](https://github.com/ayanonagon) [#4](https://github.com/venmo/slather/pull/4) [Kyle Fuller](https://github.com/kylef) [#5](https://github.com/venmo/slather/pull/5) * Add Travis automated builds [Mark Larsen](https://github.com/marklarr) [#6](https://github.com/venmo/slather/pull/6) * Use `||=` instead of `unless` [Ayaka Nonaka](https://github.com/ayanonagon) [#7](https://github.com/venmo/slather/pull/7) ## v0.0.31 * find source files via pbx proj rather than file system [Mark Larsen](https://github.com/marklarr) ## v0.0.3 * Initial Release [Mark Larsen](https://github.com/marklarr) ================================================ FILE: Gemfile ================================================ source 'https://rubygems.org' # Specify your gem's dependencies in slather.gemspec gemspec ================================================ FILE: LICENSE.txt ================================================ Copyright (c) 2014 Mark Larsen MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ ![Slather Logo](https://raw.githubusercontent.com/SlatherOrg/slather/master/docs/logo.jpg) [![Gem Version](https://badge.fury.io/rb/slather.svg)](http://badge.fury.io/rb/slather) [![Build Status](https://travis-ci.org/SlatherOrg/slather.svg?branch=master)](https://travis-ci.org/SlatherOrg/slather) [![Coverage Status](https://coveralls.io/repos/SlatherOrg/slather/badge.svg?branch=master)](https://coveralls.io/r/SlatherOrg/slather?branch=master) Generate test coverage reports for Xcode projects & hook it into CI. ### Projects that use Slather | Project | Coverage | | ------- |:--------:| | [Parsimmon](https://github.com/ayanonagon/Parsimmon) | [![Parsimmon Coverage](https://coveralls.io/repos/ayanonagon/Parsimmon/badge.svg?branch=master)](https://coveralls.io/r/ayanonagon/Parsimmon?branch=master) | | [VENCore](https://github.com/venmo/VENCore) | [![VENCore Coverage](https://coveralls.io/repos/venmo/VENCore/badge.svg?branch=master)](https://coveralls.io/r/venmo/VENCore?branch=master) | | [DAZABTest](https://github.com/dasmer/DAZABTest) | [![DAZABTest Coverage](https://coveralls.io/repos/dasmer/DAZABTest/badge.svg?branch=master)](https://coveralls.io/r/dasmer/DAZABTest?branch=master) | | [TBStateMachine](https://github.com/tarbrain/TBStateMachine) | [![TBStateMachine Coverage](https://coveralls.io/repos/tarbrain/TBStateMachine/badge.svg?branch=master)](https://coveralls.io/r/tarbrain/TBStateMachine?branch=master) | ## Installation Add this line to your application's Gemfile: ```ruby gem 'slather' ``` And then execute: ```sh $ bundle ``` Or install the gem: ```sh gem install slather ``` ## Usage Enable test coverage by ticking the *"Gather coverage data"* checkbox when editing a scheme: ![](README_Images/test_scheme.png) To verify you're ready to generate test coverage, run your test suite on your project, and then run: ```sh $ slather coverage -s --scheme YourXcodeSchemeName path/to/project.xcodeproj ``` If you use a workspace in Xcode you need to specify it: ```sh $ slather coverage -s --scheme YourXcodeSchemeName --workspace path/to/workspace.xcworkspace path/to/project.xcodeproj ``` If you use a different configuration for your tests: ```sh $ slather coverage -s --scheme YourXcodeSchemeName --configuration YourBuildConfigurationName path/to/project.xcodeproj ``` If your configuration produces a universal binary you need to specify a specific architecture to use: ```sh $ slather coverage -s --arch x86_64 --scheme YourXcodeSchemeName --configuration YourBuildConfigurationName path/to/project.xcodeproj ``` ### For multiple modules If you want to run some modules, but not all (like modules created by CocoaPods) you can do it like this: ```sh $ slather coverage --binary-basename module1 --binary-basename module2 path/to/project.xcodeproj ``` You can also add it to the `.slather.yml` file as an array: ```yml binary_basename: - module1 - module2 ``` ### Setup for Xcode 5 and 6 Run this command to enable the `Generate Test Coverage` and `Instrument Program Flow` flags for your project: ```sh $ slather setup path/to/project.xcodeproj ``` ## General Usage Notes ### Commandline Arguments Win When using both a config file (`.slather.yml`) and providing arguments via the commandline, the arguments will take precedence over the matching setting in the config file. ### `ignore` always wins over `source-files` When defining both files that should be ignored (`--ignore`, ignore) and source files to include (`--source-files`, source_files), the ignore list is checked first. If the file being scanned matches a glob in the ignore list, it will not be included. In this case, the source_file list is not checked. If the file being scanned is not in the ignore list, and source_file has been defined, the source_file list is checked. If the source file matches a glob, it will be included. ## CI Integration ### Usage with Codecov Login to [Codecov](https://codecov.io/) (no need to activate a repository, this happens automatically). Right now, `slather` supports Codecov via **all** supported CI providers [listed here](https://github.com/codecov/codecov-bash#ci-providers). Make a `.slather.yml` file: ```yml # .slather.yml coverage_service: cobertura_xml xcodeproj: path/to/project.xcodeproj scheme: YourXcodeSchemeName configuration: TestedConfiguration source_directory: path/to/sources/to/include output_directory: path/to/xml_report ignore: - ExamplePodCode/* - ProjectTestsGroup/* ``` And then in your `.travis.yml`, `circle.yml` (or after test commands in other CI providers), call `slather` after a successful build: ```yml # .travis.yml before_install: rvm use $RVM_RUBY_VERSION install: bundle install --without=documentation --path ../travis_bundle_dir after_success: - slather - bash <(curl -s https://codecov.io/bash) -f path/to/xml_report/cobertura.xml -X coveragepy -X gcov -X xcode ``` ```yml # circle.yml test: post: - bundle exec slather - bash <(curl -s https://codecov.io/bash) -f path/to/xml_report/cobertura.xml -X coveragepy -X gcov -X xcode ``` > Private repo? Add `-t :uuid-repo-token` to the codecov uploader. Read more about uploading report to Codecov [here](https://github.com/codecov/codecov-bash) ### Usage with Coveralls Login to [Coveralls](https://coveralls.io/) and enable your repository. Right now, `slather` supports Coveralls via [Travis CI](https://travis-ci.org), [CircleCI](https://circleci.com), [Jenkins](https://www.jenkins.io/), [Teamcity](https://www.jetbrains.com/teamcity/), [Buildkite](https://buildkite.com/), and [Bitrise](https://bitrise.io/). Make a `.slather.yml` file and specify the CI Service you're using: ```yml # .slather.yml ci_service: circleci | travis_ci | travis_pro | jenkins | buildkite | teamcity coverage_service: coveralls xcodeproj: path/to/project.xcodeproj scheme: YourXcodeSchemeName ignore: - ExamplePodCode/* - ProjectTestsGroup/* ``` And then in your `.travis.yml` or `circle.yml` or `github-action.yml`, call `slather` after a successful build: ```yml # .travis.yml before_install: rvm use $RVM_RUBY_VERSION install: bundle install --without=documentation --path ../travis_bundle_dir after_success: slather ``` ```yml # circle.yml test: post: - bundle exec slather ``` ```yml # github-action.yml myjob: steps: - run: | bundle config path vendor/bundle bundle install --without=documentation --jobs 4 --retry 3 - name: Extract branch name shell: bash run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})" id: get_branch - run: bundle exec slather env: GIT_BRANCH: ${{ steps.get_branch.outputs.branch }} CI_PULL_REQUEST: ${{ github.event.number }} COVERAGE_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} ``` #### Usage with Travis CI Pro To use Coveralls with Travis CI Pro (for private repos), add following lines along with other settings to `.slather.yml`: ```yml # .slather.yml ci_service: travis_pro coverage_access_token: ``` The coverage token can be found at [Coveralls](https://coveralls.io/) repo page. Or it can be passed in via the `COVERAGE_ACCESS_TOKEN` environment var. ### Cobertura To create a Cobertura XML report set `cobertura_xml` as coverage service inside your `.slather.yml`. Optionally you can define an output directory for the XML report: ```yml # .slather.yml coverage_service: cobertura_xml xcodeproj: path/to/project.xcodeproj scheme: YourXcodeSchemeName source_directory: path/to/sources/to/include output_directory: path/to/xml_report ignore: - ExamplePodCode/* - ProjectTestsGroup/* ``` Or use the command line options `--cobertura-xml` or `-x` and `--output-directory`: ```sh $ slather coverage -x --output-directory path/to/xml_report ``` ### Static HTML To create a report as static html pages, use the command line options `--html`: ```sh $ slather coverage --html --scheme YourXcodeSchemeName path/to/project.xcodeproj ``` This will make a directory named `html` in your root directory (unless `--output-directory` is specified) and will generate all the reports as static html pages inside the directory. It will print out the report's path by default, but you can also specify `--show` flag to open it in your browser automatically. By default, the generated HTML will reference locally hosted assets (js, css). You can specify the `--cdn-assets` to specify that you prefer for the generated HTML to use externally hosted assets. This can be useful if publishing the HTML file as a build artifact. ### TeamCity Reporting To report the coverage statistics to TeamCity: ```sh $ slather coverage --teamcity -s --scheme YourXcodeSchemeName ``` ### Coverage for code included via CocoaPods If you're trying to compute the coverage of code that has been included via CocoaPods, you will need to tell CocoaPods to use the Slather plugin by adding the following to your `Podfile`. ```ruby plugin 'slather' ``` You will also need to tell Slather where to find the source files for your Pod. ```yml # .slather.yml source_directory: Pods/AFNetworking ``` ### Custom Build Directory Slather will look for the test coverage files in `DerivedData` by default. If you send build output to a custom location, like [this](https://github.com/erikdoe/ocmock/blob/7f4d22b38eedf1bb9a12ab1591ac0a5d436db61a/Tools/travis.sh#L12), then you should also set the `build_directory` property in `.slather.yml` ### Building in a workspace Include the `--workspace` argument or add `workspace` to `.slather.yml` if you build your project in a workspace. For example: ```sh $ slather coverage --html --scheme YourXcodeSchemeName --workspace path/to/workspace.xcworkspace path/to/project.xcodeproj ``` ## Contributing We’d love to see your ideas for improving this library! The best way to contribute is by submitting a pull request. We’ll do our best to respond to your patch as soon as possible. You can also submit a [new GitHub issue](https://github.com/SlatherOrg/slather/issues/new) if you find bugs or have questions. :octocat: Please make sure to follow our general coding style and add test coverage for new features! ## Contributors * [@tpoulos](https://github.com/tpoulos), the perfect logo. * [@ayanonagon](https://github.com/ayanonagon) and [@kylef](https://github.com/kylef), feedback and testing. * [@jhersh](https://github.com/jhersh), CircleCI support. * [@tarbrain](https://github.com/tarbrain), Cobertura support and bugfixing. * [@ikhsan](https://github.com/ikhsan), html support. * [@martin-key](https://github.com/martin-key) and [@troyfontaine](https://github.com/troyfontaine), Github Actions support. ================================================ FILE: Rakefile ================================================ require "bundler/gem_tasks" require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec) task default: :spec ================================================ FILE: assets/highlight.pack.js ================================================ !function(e){"undefined"!=typeof exports?e(exports):(window.hljs=e({}),"function"==typeof define&&define.amd&&define("hljs",[],function(){return window.hljs}))}(function(e){function n(e){return e.replace(/&/gm,"&").replace(//gm,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0==t.index}function a(e){return/no-?highlight|plain|text/.test(e)}function i(e){var n,t,r,i=e.className+" ";if(i+=e.parentNode?e.parentNode.className:"",t=/\blang(?:uage)?-([\w-]+)\b/.exec(i))return E(t[1])?t[1]:"no-highlight";for(i=i.split(/\s+/),n=0,r=i.length;r>n;n++)if(E(i[n])||a(i[n]))return i[n]}function o(e,n){var t,r={};for(t in e)r[t]=e[t];if(n)for(t in n)r[t]=n[t];return r}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3==i.nodeType?a+=i.nodeValue.length:1==i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!=r[0].offset?e[0].offset"}function u(e){f+=""}function c(e){("start"==e.event?o:u)(e.node)}for(var s=0,f="",l=[];e.length||r.length;){var g=i();if(f+=n(a.substr(s,g[0].offset-s)),s=g[0].offset,g==e){l.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g==e&&g.length&&g[0].offset==s);l.reverse().forEach(o)}else"start"==g[0].event?l.push(g[0].node):l.pop(),c(g.splice(0,1)[0])}return f+n(a.substr(s))}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var u={},c=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");u[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?c("keyword",a.k):Object.keys(a.k).forEach(function(e){c(e,a.k[e])}),a.k=u}a.lR=t(a.l||/\b\w+\b/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),void 0===a.r&&(a.r=1),a.c||(a.c=[]);var s=[];a.c.forEach(function(e){e.v?e.v.forEach(function(n){s.push(o(e,n))}):s.push("self"==e?a:e)}),a.c=s,a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var f=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=f.length?t(f.join("|"),!0):{exec:function(){return null}}}}r(e)}function f(e,t,a,i){function o(e,n){for(var t=0;t";return i+=e+'">',i+n+o}function p(){if(!L.k)return n(B);var e="",t=0;L.lR.lastIndex=0;for(var r=L.lR.exec(B);r;){e+=n(B.substr(t,r.index-t));var a=g(L,r);a?(y+=a[1],e+=h(a[0],n(r[0]))):e+=n(r[0]),t=L.lR.lastIndex,r=L.lR.exec(B)}return e+n(B.substr(t))}function d(){if(L.sL&&!x[L.sL])return n(B);var e=L.sL?f(L.sL,B,!0,M[L.sL]):l(B);return L.r>0&&(y+=e.r),"continuous"==L.subLanguageMode&&(M[L.sL]=e.top),h(e.language,e.value,!1,!0)}function b(){return void 0!==L.sL?d():p()}function v(e,t){var r=e.cN?h(e.cN,"",!0):"";e.rB?(k+=r,B=""):e.eB?(k+=n(t)+r,B=""):(k+=r,B=t),L=Object.create(e,{parent:{value:L}})}function m(e,t){if(B+=e,void 0===t)return k+=b(),0;var r=o(t,L);if(r)return k+=b(),v(r,t),r.rB?0:t.length;var a=u(L,t);if(a){var i=L;i.rE||i.eE||(B+=t),k+=b();do L.cN&&(k+=""),y+=L.r,L=L.parent;while(L!=a.parent);return i.eE&&(k+=n(t)),B="",a.starts&&v(a.starts,""),i.rE?0:t.length}if(c(t,L))throw new Error('Illegal lexeme "'+t+'" for mode "'+(L.cN||"")+'"');return B+=t,t.length||1}var N=E(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var R,L=i||N,M={},k="";for(R=L;R!=N;R=R.parent)R.cN&&(k=h(R.cN,"",!0)+k);var B="",y=0;try{for(var C,j,I=0;;){if(L.t.lastIndex=I,C=L.t.exec(t),!C)break;j=m(t.substr(I,C.index-I),C[0]),I=C.index+j}for(m(t.substr(I)),R=L;R.parent;R=R.parent)R.cN&&(k+="");return{r:y,value:k,language:e,top:L}}catch(O){if(-1!=O.message.indexOf("Illegal"))return{r:0,value:n(t)};throw O}}function l(e,t){t=t||w.languages||Object.keys(x);var r={r:0,value:n(e)},a=r;return t.forEach(function(n){if(E(n)){var t=f(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}}),a.language&&(r.second_best=a),r}function g(e){return w.tabReplace&&(e=e.replace(/^((<[^>]+>|\t)+)/gm,function(e,n){return n.replace(/\t/g,w.tabReplace)})),w.useBR&&(e=e.replace(/\n/g,"
")),e}function h(e,n,t){var r=n?R[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function p(e){var n=i(e);if(!a(n)){var t;w.useBR?(t=document.createElementNS("http://www.w3.org/1999/xhtml","div"),t.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):t=e;var r=t.textContent,o=n?f(n,r,!0):l(r),s=u(t);if(s.length){var p=document.createElementNS("http://www.w3.org/1999/xhtml","div");p.innerHTML=o.value,o.value=c(s,u(p),r)}o.value=g(o.value),e.innerHTML=o.value,e.className=h(e.className,n,o.language),e.result={language:o.language,re:o.r},o.second_best&&(e.second_best={language:o.second_best.language,re:o.second_best.r})}}function d(e){w=o(w,e)}function b(){if(!b.called){b.called=!0;var e=document.querySelectorAll("pre code");Array.prototype.forEach.call(e,p)}}function v(){addEventListener("DOMContentLoaded",b,!1),addEventListener("load",b,!1)}function m(n,t){var r=x[n]=t(e);r.aliases&&r.aliases.forEach(function(e){R[e]=n})}function N(){return Object.keys(x)}function E(e){return x[e]||x[R[e]]}var w={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},x={},R={};return e.highlight=f,e.highlightAuto=l,e.fixMarkup=g,e.highlightBlock=p,e.configure=d,e.initHighlighting=b,e.initHighlightingOnLoad=v,e.registerLanguage=m,e.listLanguages=N,e.getLanguage=E,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="\\b(0[xX][a-fA-F0-9]+|(\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",bK:"TODO FIXME NOTE BUG XXX",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e});hljs.registerLanguage("json",function(e){var t={literal:"true false null"},i=[e.QSM,e.CNM],l={cN:"value",e:",",eW:!0,eE:!0,c:i,k:t},c={b:"{",e:"}",c:[{cN:"attribute",b:'\\s*"',e:'"\\s*:\\s*',eB:!0,eE:!0,c:[e.BE],i:"\\n",starts:l}],i:"\\S"},n={b:"\\[",e:"\\]",c:[e.inherit(l,{cN:null})],i:"\\S"};return i.splice(i.length,0,c,n),{c:i,k:t,i:"\\S"}});hljs.registerLanguage("objectivec",function(e){var t={cN:"built_in",b:"(AV|CA|CF|CG|CI|MK|MP|NS|UI)\\w+"},i={keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},o=/[a-zA-Z@][a-zA-Z0-9_]*/,n="@interface @class @protocol @implementation";return{aliases:["mm","objc","obj-c"],k:i,l:o,i:""}]}]},{cN:"class",b:"("+n.split(" ").join("|")+")\\b",e:"({|$)",eE:!0,k:n,l:o,c:[e.UTM]},{cN:"variable",b:"\\."+e.UIR,r:0}]}});hljs.registerLanguage("cpp",function(t){var e={cN:"keyword",b:"[a-z\\d_]*_t"},r={keyword:"false int float while private char catch export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const struct for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using true class asm case typeid short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignof constexpr decltype noexcept nullptr static_assert thread_local restrict _Bool complex _Complex _Imaginary atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong",built_in:"std string cin cout cerr clog stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf"};return{aliases:["c","cc","h","c++","h++","hpp"],k:r,i:""]',k:"include",i:"\\n"},t.CLCM]},{b:"\\b(deque|list|queue|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<",e:">",k:r,c:["self",e]},{b:t.IR+"::",k:r},{bK:"new throw return else",r:0},{cN:"function",b:"("+t.IR+"\\s+)+"+t.IR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:r,c:[{b:t.IR+"\\s*\\(",rB:!0,c:[t.TM],r:0},{cN:"params",b:/\(/,e:/\)/,k:r,r:0,c:[t.CBCM]},t.CLCM,t.CBCM]}]}}); ================================================ FILE: assets/slather.css ================================================ /* -------------------------------------------------------- Slather stylesheet version: 0.1 author: Ikhsan Assaat (@ixnixnixn) ----------------------------------------------------------*/ /* General */ html { position: relative; min-height: 100%; } body { font: 16px "Helvetica", sans-serif; margin: 0 0 120px; color: #333; } .row { margin: 0 2em; } /* Header */ header { margin-top: 1em; } header img { width: auto; height: 120px; } /* Coverage */ #reports > h2 { margin-bottom: 0; } #reports > h4 { margin-top: 5px; } .percentage { padding: 4px ; font-weight: bold; } .cov_high { color: #67CF7C; } .cov_medium { color: #F89404; } .cov_low { color: #F86769; } .cov_title { margin-bottom: 0; } .cov_subtitle { margin-top: 0.2em; } .cov_filepath { font-style: italic; } /* Index Table */ table.coverage_list { width: 90%; min-width: 400px; } table.coverage_list th, table.coverage_list td { padding: .6em .5em; text-align: left; } table.coverage_list th.col_num { width: 70px; } table.coverage_list th.col_percent { width: 75px; } table.coverage_list thead, tfoot { background: #FDCD9B; } table.coverage_list tbody tr:hover { background: #FCF2E6; } table.coverage_list tbody td { border-bottom: 1px solid #CCC; } table.coverage_list td a { color: #333; text-decoration: none; border-bottom: 1px dotted; } table.coverage_list td a:hover { border-bottom: none; } /* Source Code */ table.source_code { width: 100%; max-width: 1200px; min-width: 400px; font-size: 13px; border-spacing: 0; background: #FCF2E6; padding: 1.2em 1em; margin-bottom: 2em; } table.source_code td { padding-bottom: 0.3em; } code.missed { background-color: rgba(255, 73, 76, 0.3); } table.source_code tr.missed td { background-color: rgba(248, 103, 105, 0.2); } table.source_code tr.covered td { background-color: rgba(103, 207, 124, 0.2); } table.source_code td.num { border-right: 1px rgba(0,0,0,0.1) solid; text-align: right; padding-right: 1em; width: 30px; } table.source_code td.src { border-left: 1px rgba(255,255,255,0.7) solid; padding-left: 1em; } table.source_code td.src pre { white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -pre-wrap; white-space: -o-pre-wrap; word-wrap: break-word; margin: 0; } table.source_code td.src pre code { font: 13px "Menlo", "Courier New"; } table.source_code td.coverage { text-align: right; padding-right: 0.5em; } /* Footer */ footer { background-color: #67CDCF; height: 80px; position: absolute; left: 0; bottom: 0; width: 100%; overflow:hidden; } footer p, footer a { color: #ffffff; font-weight: bold; text-align: center; } /* ---------------------------------------------------------- Syntax Highlighting using highlight.js (https://highlightjs.org) ------------------------------------------------------------- */ .hljs { display: inline-block; overflow-x: auto; -webkit-text-size-adjust: none; } .hljs, .hljs-subst, .hljs-tag .hljs-title, .nginx .hljs-title { color: #333; } .hljs-string, .hljs-title, .hljs-constant, .hljs-parent, .hljs-tag .hljs-value, .hljs-rule .hljs-value, .hljs-preprocessor, .hljs-pragma, .hljs-name, .haml .hljs-symbol, .ruby .hljs-symbol, .ruby .hljs-symbol .hljs-string, .hljs-template_tag, .django .hljs-variable, .smalltalk .hljs-class, .hljs-addition, .hljs-flow, .hljs-stream, .bash .hljs-variable, .pf .hljs-variable, .apache .hljs-tag, .apache .hljs-cbracket, .tex .hljs-command, .tex .hljs-special, .erlang_repl .hljs-function_or_atom, .asciidoc .hljs-header, .markdown .hljs-header, .coffeescript .hljs-attribute, .tp .hljs-variable { color: #D14F4F; } .smartquote, .hljs-comment, .hljs-annotation, .diff .hljs-header, .hljs-chunk, .asciidoc .hljs-blockquote, .markdown .hljs-blockquote { color: #888; } .hljs-number, .hljs-date, .hljs-regexp, .hljs-literal, .hljs-hexcolor, .smalltalk .hljs-symbol, .smalltalk .hljs-char, .go .hljs-constant, .hljs-change, .lasso .hljs-variable, .makefile .hljs-variable, .asciidoc .hljs-bullet, .markdown .hljs-bullet, .asciidoc .hljs-link_url, .markdown .hljs-link_url { color: #05A5A8; } .hljs-label, .ruby .hljs-string, .hljs-decorator, .hljs-filter .hljs-argument, .hljs-localvars, .hljs-array, .hljs-attr_selector, .hljs-important, .hljs-pseudo, .hljs-pi, .haml .hljs-bullet, .hljs-doctype, .hljs-deletion, .hljs-envvar, .hljs-shebang, .apache .hljs-sqbracket, .nginx .hljs-built_in, .tex .hljs-formula, .erlang_repl .hljs-reserved, .hljs-prompt, .asciidoc .hljs-link_label, .markdown .hljs-link_label, .vhdl .hljs-attribute, .clojure .hljs-attribute, .asciidoc .hljs-attribute, .lasso .hljs-attribute, .coffeescript .hljs-property, .hljs-phony { color: #087599; } .hljs-keyword, .hljs-id, .hljs-title, .hljs-built_in, .css .hljs-tag, .hljs-doctag, .smalltalk .hljs-class, .hljs-winutils, .bash .hljs-variable, .pf .hljs-variable, .apache .hljs-tag, .hljs-type, .hljs-typename, .tex .hljs-command, .asciidoc .hljs-strong, .markdown .hljs-strong, .hljs-request, .hljs-status, .tp .hljs-data, .tp .hljs-io { font-weight: bold; } .asciidoc .hljs-emphasis, .markdown .hljs-emphasis, .tp .hljs-units { font-style: italic; } .nginx .hljs-built_in { font-weight: normal; } .coffeescript .javascript, .javascript .xml, .lasso .markup, .tex .hljs-formula, .xml .javascript, .xml .vbscript, .xml .css, .xml .hljs-cdata { opacity: 0.5; } /* ------------------------------------------------------- Sorting & Filtering with List.js (http://www.listjs.com/) ------------------------------------------------------- */ input.search { border:solid 1px #ccc; border-radius: 4px; padding:7px; margin-bottom:10px; font-size: 12px; } input.search:focus { outline:none; border-color:#aaa; } th.sort::-moz-selection { background:transparent; } th.sort::selection { background:transparent; } th.sort { cursor:pointer; } th.sort:after { content:''; display:inline-block; width: 0; height: 0; position: relative; top: -3px; right: -6px; border-width:0 4px 4px; border-style:solid; border-color:#404040 transparent; visibility:hidden; } th.sort:hover:after { visibility:visible; } th.sort.desc:after, th.sort.asc:after, th.sort.asc:hover:after { visibility:visible; opacity:0.6; } th.sort.desc:after { border-bottom:none; border-width:4px 4px 0; } ================================================ FILE: bin/slather ================================================ #!/usr/bin/env ruby require 'clamp' require 'yaml' require_relative '../lib/slather' require_relative '../lib/slather/command/coverage_command' require_relative '../lib/slather/command/setup_command' require_relative '../lib/slather/command/version_command' class MainCommand < Clamp::Command self.default_subcommand = "coverage" subcommand 'setup', 'Configures a .xcodeproj for test coverage generation', SetupCommand subcommand 'coverage', 'Computes coverage for the supplied project', CoverageCommand subcommand 'version', 'Prints slather version', VersionCommand end MainCommand.run ================================================ FILE: lib/cocoapods_plugin.rb ================================================ require_relative 'slather' Pod::HooksManager.register('slather', :post_install) do |installer_context| sandbox_root = installer_context.sandbox_root sandbox = Pod::Sandbox.new(sandbox_root) project = Xcodeproj::Project.open(sandbox.project_path) project.slather_setup_for_coverage() project.save() end ================================================ FILE: lib/slather/command/coverage_command.rb ================================================ class CoverageCommand < Clamp::Command parameter "[PROJECT]", "Path to the xcodeproj", :attribute_name => :xcodeproj_path option ["--travis", "-t"], :flag, "Indicate that the builds are running on Travis CI" option ["--travispro"], :flag, "Indicate that the builds are running on Travis Pro CI" option ["--circleci"], :flag, "Indicate that the builds are running on CircleCI" option ["--jenkins"], :flag, "Indicate that the builds are running on Jenkins" option ["--buildkite"], :flag, "Indicate that the builds are running on Buildkite" option ["--teamcity"], :flag, "Indicate that the builds are running on TeamCity" option ["--github"], :flag, "Indicate that the builds are running on Github Actions" option ["--coveralls", "-c"], :flag, "Post coverage results to coveralls" option ["--simple-output", "-s"], :flag, "Output coverage results to the terminal" option ["--gutter-json", "-g"], :flag, "Output coverage results as Gutter JSON format" option ["--cobertura-xml", "-x"], :flag, "Output coverage results as Cobertura XML format" option ["--sonarqube-xml", "-sq"], :flag, "Output coverage results as SonarQube XML format" option ["--llvm-cov", "-r"], :flag, "Output coverage as llvm-cov format" option ["--json"], :flag, "Output coverage results as simple JSON" option ["--html"], :flag, "Output coverage results as static html pages" option ["--show"], :flag, "Indicate that the static html pages will open automatically" option ["--cdn-assets"], :flag, "Indicate that the static html pages will load assets from a CDN" option ["--build-directory", "-b"], "BUILD_DIRECTORY", "The directory where gcno files will be written to. Defaults to derived data." option ["--source-directory"], "SOURCE_DIRECTORY", "The directory where your source files are located." option ["--output-directory"], "OUTPUT_DIRECTORY", "The directory where your Cobertura XML report will be written to." option ["--ignore", "-i"], "IGNORE", "ignore files conforming to a path", :multivalued => true option ["--verbose", "-v"], :flag, "Enable verbose mode" option ["--input-format"], "INPUT_FORMAT", "Input format (gcov, profdata)" option ["--scheme"], "SCHEME", "The scheme for which the coverage was generated" option ["--configuration"], "CONFIGURATION", "The configuration for test that the project was set" option ["--workspace"], "WORKSPACE", "The workspace that the project was built in" option ["--binary-file"], "BINARY_FILE", "The binary file against which the coverage will be run", :multivalued => true option ["--binary-basename"], "BINARY_BASENAME", "Basename of the file against which the coverage will be run", :multivalued => true option ["--arch"], "ARCH", "Architecture to use from universal binaries" option ["--source-files"], "SOURCE_FILES", "A Dir.glob compatible pattern used to limit the lookup to specific source files. Ignored in gcov mode.", :multivalued => true option ["--decimals"], "DECIMALS", "The amount of decimals to use for % coverage reporting" option ["--ymlfile"], "YMLFILE", "Relative path to a file used in place of '.slather.yml'" def execute puts "Slathering..." setup_ymlfile # MUST be the first setup setup_service_name setup_ignore_list setup_build_directory setup_source_directory setup_output_directory setup_coverage_service setup_verbose_mode setup_input_format setup_scheme setup_configuration setup_workspace setup_binary_file setup_binary_basename setup_arch setup_source_files setup_decimals project.configure post puts "Slathered" end def setup_ymlfile Slather::Project.yml_filename = ymlfile if ymlfile end def setup_build_directory project.build_directory = build_directory if build_directory end def setup_source_directory project.source_directory = source_directory if source_directory end def setup_output_directory project.output_directory = output_directory if output_directory end def setup_ignore_list project.ignore_list = ignore_list if !ignore_list.empty? end def setup_service_name if travis? project.ci_service = :travis_ci elsif travispro? project.ci_service = :travis_pro elsif circleci? project.ci_service = :circleci elsif jenkins? project.ci_service = :jenkins elsif buildkite? project.ci_service = :buildkite elsif teamcity? project.ci_service = :teamcity elsif github? project.ci_service = :github end end def post project.post end def project @project ||= begin xcodeproj_path_to_open = xcodeproj_path || Slather::Project.yml["xcodeproj"] if xcodeproj_path_to_open project = Slather::Project.open(xcodeproj_path_to_open) else raise StandardError, "Must provide an xcodeproj either via the 'slather [SUBCOMMAND] [PROJECT].xcodeproj' command or through .slather.yml" end end end def setup_coverage_service if coveralls? project.coverage_service = :coveralls elsif simple_output? project.coverage_service = :terminal elsif gutter_json? project.coverage_service = :gutter_json elsif cobertura_xml? project.coverage_service = :cobertura_xml elsif llvm_cov? project.coverage_service = :llvm_cov elsif html? project.coverage_service = :html project.show_html = show? project.cdn_assets = cdn_assets? elsif json? project.coverage_service = :json elsif sonarqube_xml? project.coverage_service = :sonarqube_xml end end def setup_verbose_mode project.verbose_mode = verbose? end def setup_input_format project.input_format = input_format end def setup_scheme project.scheme = scheme end def setup_configuration project.configuration = configuration end def setup_workspace project.workspace = workspace end def setup_binary_file project.binary_file = binary_file_list if !binary_file_list.empty? end def setup_binary_basename project.binary_basename = binary_basename_list if !binary_basename_list.empty? end def setup_arch project.arch = arch end def setup_source_files project.source_files = source_files_list if !source_files_list.empty? end def setup_decimals project.decimals = decimals if decimals end end ================================================ FILE: lib/slather/command/setup_command.rb ================================================ class SetupCommand < Clamp::Command parameter "[PROJECT]", "Path to the .xcodeproj", :attribute_name => :xcodeproj_path option ["--format"], "FORMAT", "Type of coverage to use (gcov, clang, auto)" option ["--ymlfile"], "YMLFILE", "Relative path to a file used in place of '.slather.yml'" def execute setup_ymlfile xcodeproj_path_to_open = xcodeproj_path || Slather::Project.yml["xcodeproj"] unless xcodeproj_path_to_open raise StandardError, "Must provide a .xcodeproj either via the 'slather [SUBCOMMAND] [PROJECT].xcodeproj' command or through .slather.yml" end project = Slather::Project.open(xcodeproj_path_to_open) project.setup_for_coverage(format ? format.to_sym : :auto) project.save end def setup_ymlfile Slather::Project.yml_filename = ymlfile if ymlfile end end ================================================ FILE: lib/slather/command/version_command.rb ================================================ class VersionCommand < Clamp::Command def execute puts "slather #{Slather::VERSION}" end end ================================================ FILE: lib/slather/coverage_file.rb ================================================ require_relative 'coverage_info' require_relative 'coveralls_coverage' module Slather class CoverageFile include CoverageInfo include CoverallsCoverage attr_accessor :project, :gcno_file_pathname def initialize(project, gcno_file_pathname) self.project = project self.gcno_file_pathname = Pathname(gcno_file_pathname) end def source_file_pathname @source_file_pathname ||= begin base_filename = gcno_file_pathname.basename.sub_ext("") path = nil if project.source_directory path = Dir["#{project.source_directory}/**/#{base_filename}.{#{supported_file_extensions.join(",")}}"].first path &&= Pathname(path) else pbx_file = project.files.detect { |pbx_file| current_base_filename = pbx_file.real_path.basename ext_name = File.extname(current_base_filename.to_s)[1..-1] current_base_filename.sub_ext("") == base_filename && supported_file_extensions.include?(ext_name) } path = pbx_file && pbx_file.real_path end path end end def source_file File.new(source_file_pathname) end def source_data source_file.read end def gcov_data @gcov_data ||= begin gcov_data = "" Dir.chdir(project.project_dir) do gcov_output = `gcov "#{source_file_pathname}" --object-directory "#{gcno_file_pathname.parent}" --branch-probabilities --branch-counts` # Sometimes gcov makes gcov files for Cocoa Touch classes, like NSRange. Ignore and delete later. gcov_files_created = gcov_output.scan(/creating '(.+\..+\.gcov)'/) gcov_file_name = "./#{source_file_pathname.basename}.gcov" if File.exist?(gcov_file_name) gcov_data = File.new(gcov_file_name).read else gcov_data = "" end gcov_files_created.each { |file| FileUtils.rm_f(file) } end gcov_data end end def all_lines unless cleaned_gcov_data.empty? first_line_start = cleaned_gcov_data =~ /^\s+(-|#+|[0-9+]):\s+1:/ cleaned_gcov_data[first_line_start..-1].split("\n").map else [] end end def cleaned_gcov_data data = gcov_data.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '').gsub(/^function(.*) called [0-9]+ returned [0-9]+% blocks executed(.*)$\r?\n/, '') data.gsub(/^branch(.*)$\r?\n/, '') end def raw_data self.gcov_data end def line_coverage_data unless cleaned_gcov_data.empty? first_line_start = cleaned_gcov_data =~ /^\s+(-|#+|[0-9+]):\s+1:/ cleaned_gcov_data[first_line_start..-1].split("\n").map do |line| coverage_for_line(line) end else [] end end def line_number_in_line(line) line.split(':')[1].strip.to_i end def coverage_for_line(line) line =~ /^(.+?):/ match = $1.strip case match when /[0-9]+/ match.to_i when /#+/ 0 when "-" nil end end def branch_coverage_data @branch_coverage_data ||= begin branch_coverage_data = Hash.new gcov_data.scan(/(^(\s+(-|#+|[0-9]+):\s+[1-9]+:(.*)$\r?\n)(^branch\s+[0-9]+\s+[a-zA-Z0-9]+\s+[a-zA-Z0-9]+$\r?\n)+)+/) do |data| lines = data[0].split("\n") line_number = lines[0].split(':')[1].strip.to_i branch_coverage_data[line_number] = lines[1..-1].map do |line| if line.split(' ')[2].strip == "never" 0 else line.split(' ')[3].strip.to_i end end end branch_coverage_data end end def source_file_basename File.basename(source_file_pathname, '.m') end def line_number_separator ":" end def supported_file_extensions ["cpp", "mm", "m"] end private :supported_file_extensions end end ================================================ FILE: lib/slather/coverage_info.rb ================================================ module Slather module CoverageInfo def num_lines_tested line_coverage_data.compact.select { |cd| cd > 0 }.count end def num_lines_testable line_coverage_data.compact.count end def rate_lines_tested if num_lines_testable > 0 (num_lines_tested / num_lines_testable.to_f) else 0 end end def percentage_lines_tested if num_lines_testable == 0 100 else rate_lines_tested * 100 end end def branch_coverage_data_for_statement_on_line(line_number) branch_coverage_data[line_number] || [] end def num_branches_for_statement_on_line(line_number) branch_coverage_data_for_statement_on_line(line_number).length end def num_branch_hits_for_statement_on_line(line_number) branch_coverage_data_for_statement_on_line(line_number).count { |hit_count| hit_count > 0 } end def rate_branch_coverage_for_statement_on_line(line_number) branch_data = branch_coverage_data_for_statement_on_line(line_number) if branch_data.empty? 0.0 else (num_branch_hits_for_statement_on_line(line_number) / branch_data.length.to_f) end end def percentage_branch_coverage_for_statement_on_line(line_number) rate_branch_coverage_for_statement_on_line(line_number) * 100 end def num_branches_testable branch_coverage_data.keys.reduce(0) do |sum, line_number| sum += num_branches_for_statement_on_line(line_number) end end def num_branches_tested branch_coverage_data.keys.reduce(0) do |sum, line_number| sum += num_branch_hits_for_statement_on_line(line_number) end end def rate_branches_tested if (num_branches_testable > 0) (num_branches_tested / num_branches_testable.to_f) else 0.0 end end def source_file_pathname_relative_to_repo_root source_file_pathname.realpath.relative_path_from(Pathname("./").realpath) end def ignored? project.ignore_list.any? do |ignore| File.fnmatch(ignore, source_file_pathname_relative_to_repo_root) end end def include_file? rv = true # default true return value to fix https://github.com/SlatherOrg/slather/issues/561 project.source_files.any? do |include| rv = File.fnmatch(include, source_file_pathname_relative_to_repo_root) end rv end end end ================================================ FILE: lib/slather/coverage_service/cobertura_xml_output.rb ================================================ require 'nokogiri' require 'date' module Slather module CoverageService module CoberturaXmlOutput def coverage_file_class if input_format == "profdata" Slather::ProfdataCoverageFile else Slather::CoverageFile end end private :coverage_file_class def post cobertura_xml_report = create_xml_report(coverage_files) store_report(cobertura_xml_report) end def store_report(report) output_file = 'cobertura.xml' if output_directory FileUtils.mkdir_p(output_directory) output_file = File.join(output_directory, output_file) end File.write(output_file, report.to_s) end def grouped_coverage_files(coverage_files) groups = Hash.new coverage_files.each do |coverage_file| next if coverage_file == nil path = File.dirname(coverage_file.source_file_pathname_relative_to_repo_root) if groups[path] == nil groups[path] = Array.new end groups[path].push(coverage_file) end groups end def create_xml_report(coverage_files) total_project_lines = 0 total_project_lines_tested = 0 total_project_line_rate = '%.16f' % 1.0 total_project_branches = 0 total_project_branches_tested = 0 total_project_branch_rate = '%.16f' % 1.0 create_empty_xml_report coverage_node = @doc.root source_node = @doc.at_css "source" source_node.content = Pathname.pwd.to_s packages_node = @doc.at_css "packages" # group files by path grouped_coverage_files(coverage_files).each do |path , package_coverage_files| package_node = Nokogiri::XML::Node.new "package", @doc package_node.parent = packages_node classes_node = Nokogiri::XML::Node.new "classes", @doc classes_node.parent = package_node package_node['name'] = path.gsub(/\//, '.') total_package_lines = 0 total_package_lines_tested = 0 total_package_lines_rate = '%.16f' % 1.0 total_package_branches = 0 total_package_branches_tested = 0 total_package_branch_rate = '%.16f' % 1.0 package_coverage_files.each do |package_coverage_file| class_node = create_class_node(package_coverage_file) class_node.parent = classes_node total_package_lines += package_coverage_file.num_lines_testable total_package_lines_tested += package_coverage_file.num_lines_tested total_package_branches += package_coverage_file.num_branches_testable total_package_branches_tested += package_coverage_file.num_branches_tested end if (total_package_lines > 0) total_package_line_rate = '%.16f' % (total_package_lines_tested / total_package_lines.to_f) end if (total_package_branches > 0) total_package_branch_rate = '%.16f' % (total_package_branches_tested / total_package_branches.to_f) end package_node['line-rate'] = total_package_line_rate package_node['branch-rate'] = total_package_branch_rate package_node['complexity'] = '0.0' total_project_lines += total_package_lines total_project_lines_tested += total_package_lines_tested total_project_branches += total_package_branches total_project_branches_tested += total_package_branches_tested end if (total_project_lines > 0) total_project_line_rate = '%.16f' % (total_project_lines_tested / total_project_lines.to_f) end if (total_project_branches > 0) total_project_branch_rate = '%.16f' % (total_project_branches_tested / total_project_branches.to_f) end coverage_node['line-rate'] = total_project_line_rate coverage_node['branch-rate'] = total_project_branch_rate coverage_node['lines-covered'] = total_project_lines_tested coverage_node['lines-valid'] = total_project_lines coverage_node['branches-covered'] = total_project_branches_tested coverage_node['branches-valid'] = total_project_branches coverage_node['complexity'] = "0.0" coverage_node['timestamp'] = DateTime.now.strftime('%s') coverage_node['version'] = "Slather #{Slather::VERSION}" @doc.to_xml end def create_class_node(coverage_file) filename = coverage_file.source_file_basename filepath = coverage_file.source_file_pathname_relative_to_repo_root.to_s class_node = Nokogiri::XML::Node.new "class", @doc class_node['name'] = filename class_node['filename'] = filepath class_node['line-rate'] = '%.16f' % [(coverage_file.num_lines_testable > 0) ? coverage_file.rate_lines_tested : 1.0] class_node['branch-rate'] = '%.16f' % [(coverage_file.num_branches_testable > 0) ? coverage_file.rate_branches_tested : 1.0] class_node['complexity'] = '0.0' methods_node = Nokogiri::XML::Node.new "methods", @doc methods_node.parent = class_node lines_node = Nokogiri::XML::Node.new "lines", @doc lines_node.parent = class_node coverage_file.all_lines.each do |line| if coverage_file.coverage_for_line(line) line_node = create_line_node(line, coverage_file) line_node.parent = lines_node end end class_node end def create_line_node(line, coverage_file) line_number = coverage_file.line_number_in_line(line) line_node = Nokogiri::XML::Node.new "line", @doc line_node['number'] = line_number line_node['branch'] = "false" line_node['hits'] = coverage_file.coverage_for_line(line) unless coverage_file.branch_coverage_data_for_statement_on_line(line_number).empty? line_node['branch'] = "true" conditions_node = Nokogiri::XML::Node.new "conditions", @doc conditions_node.parent = line_node condition_node = Nokogiri::XML::Node.new "condition", @doc condition_node.parent = conditions_node condition_node['number'] = "0" condition_node['type'] = "jump" branches_testable = coverage_file.num_branches_for_statement_on_line(line_number) branch_hits = coverage_file.num_branch_hits_for_statement_on_line(line_number) condition_coverage = coverage_file.percentage_branch_coverage_for_statement_on_line(line_number) condition_node['coverage'] = "#{condition_coverage.to_i}%" line_node['condition-coverage'] = "#{condition_coverage.to_i}% (#{branch_hits}/#{branches_testable})" end line_node end def create_empty_xml_report builder = Nokogiri::XML::Builder.new do |xml| xml.doc.create_internal_subset( 'coverage', nil, "https://raw.githubusercontent.com/cobertura/cobertura/master/cobertura/src/site/htdocs/xml/coverage-04.dtd" ) xml.coverage do xml.sources do xml.source end xml.packages end end @doc = builder.doc end end end end ================================================ FILE: lib/slather/coverage_service/coveralls.rb ================================================ module Slather module CoverageService module Coveralls def coverage_file_class if input_format == "profdata" Slather::ProfdataCoverageFile else Slather::CoverageFile end end private :coverage_file_class def travis_job_id ENV['TRAVIS_JOB_ID'] end private :travis_job_id def circleci_job_id ENV['CIRCLE_BUILD_NUM'] end private :circleci_job_id def circleci_pull_request ENV['CIRCLE_PR_NUMBER'] || ENV['CI_PULL_REQUEST'] || "" end private :circleci_pull_request def teamcity_job_id ENV['TC_BUILD_NUMBER'] end private :teamcity_job_id def jenkins_job_id ENV['BUILD_ID'] end private :jenkins_job_id def github_job_id ENV['GITHUB_RUN_ID'] end private :github_job_id def bitrise_job_id ENV['BITRISE_BUILD_NUMBER'] end private :bitrise_job_id def bitrise_pull_request ENV['BITRISE_PULL_REQUEST'] end private :bitrise_pull_request def github_pull_request ENV['CI_PULL_REQUEST'] || "" end private :github_pull_request def github_repo_name ENV['GITHUB_REPOSITORY'] || "" end private :github_repo_name def jenkins_branch_name branch_name = ENV['GIT_BRANCH'] || ENV['BRANCH_NAME'] if branch_name.include? 'origin/' branch_name[7...branch_name.length] else branch_name end end private :jenkins_branch_name def teamcity_branch_name ENV['GIT_BRANCH'] || `git ls-remote --heads origin | grep $(git rev-parse HEAD) | cut -d / -f 3-`.chomp end private :teamcity_branch_name def github_branch_name ENV['GIT_BRANCH'] || `git ls-remote --heads origin | grep $(git rev-parse HEAD) | cut -d / -f 3-`.chomp end private :github_branch_name def bitrise_branch_name ENV['BITRISE_GIT_BRANCH'] || `git ls-remote --heads origin | grep $(git rev-parse HEAD) | cut -d / -f 3-`.chomp end private :bitrise_branch_name def buildkite_job_id ENV['BUILDKITE_BUILD_NUMBER'] end private :buildkite_job_id def buildkite_pull_request ENV['BUILDKITE_PULL_REQUEST'] end private :buildkite_pull_request def jenkins_git_info { head: { id: ENV['sha1'], author_name: ENV['ghprbActualCommitAuthor'], message: ENV['ghprbPullTitle'] }, branch: jenkins_branch_name } end private :jenkins_git_info def teamcity_git_info { head: { :id => (`git log --format=%H -n 1 HEAD`.chomp || ""), :author_name => (`git log --format=%an -n 1 HEAD`.chomp || ""), :author_email => (`git log --format=%ae -n 1 HEAD`.chomp || ""), :message => (`git log --format=%s -n 1 HEAD`.chomp || "") }, :branch => teamcity_branch_name } end private :teamcity_git_info def circleci_build_url "https://circleci.com/gh/" + ENV['CIRCLE_PROJECT_USERNAME'] || "" + "/" + ENV['CIRCLE_PROJECT_REPONAME'] || "" + "/" + ENV['CIRCLE_BUILD_NUM'] || "" end private :circleci_build_url def circleci_git_info { :head => { :id => (ENV['CIRCLE_SHA1'] || ""), :author_name => (ENV['CIRCLE_PR_USERNAME'] || ENV['CIRCLE_USERNAME'] || ""), :message => (`git log --format=%s -n 1 HEAD`.chomp || "") }, :branch => (ENV['CIRCLE_BRANCH'] || "") } end private :circleci_git_info def buildkite_git_info { :head => { :id => ENV['BUILDKITE_COMMIT'], :author_name => (`git log --format=%an -n 1 HEAD`.chomp || ""), :author_email => (`git log --format=%ae -n 1 HEAD`.chomp || ""), :message => (`git log --format=%s -n 1 HEAD`.chomp || "") }, :branch => ENV['BUILDKITE_BRANCH'] } end def buildkite_build_url "https://buildkite.com/" + ENV['BUILDKITE_PROJECT_SLUG'] + "/builds/" + ENV['BUILDKITE_BUILD_NUMBER'] + "#" end def github_git_info { :head => { :id => ENV['GITHUB_SHA'], :author_name => ENV['GITHUB_ACTOR'], :message => (`git log --format=%s -n 1 HEAD`.chomp || "") }, :branch => github_branch_name } end private :github_git_info def bitrise_git_info { :head => { :id => ENV['BITRISE_GIT_COMMIT'], :committer_name => (ENV['GIT_CLONE_COMMIT_AUTHOR_NAME'] || `git log --format=%an -n 1 HEAD`.chomp || ""), :committer_email => (ENV['GIT_CLONE_COMMIT_AUTHOR_EMAIL'] || `git log --format=%ae -n 1 HEAD`.chomp || ""), :message => (ENV['BITRISE_GIT_MESSAGE'] || `git log --format=%s -n 1 HEAD`.chomp || "") }, :branch => bitrise_branch_name } end private :bitrise_git_info def github_build_url "https://github.com/" + ENV['GITHUB_REPOSITORY'] + "/actions/runs/" + ENV['GITHUB_RUN_ID'] end private :github_build_url def is_parallel ENV['IS_PARALLEL'] != nil end private :is_parallel def github_job_name ENV['GITHUB_JOB'] end private :github_job_name def coveralls_coverage_data if ci_service == :travis_ci || ci_service == :travis_pro if travis_job_id if ci_service == :travis_ci if coverage_access_token.to_s.strip.length > 0 raise StandardError, "Access token is set. Uploading coverage data for public repositories doesn't require an access token." end { :service_job_id => travis_job_id, :service_name => "travis-ci", :source_files => coverage_files.map(&:as_json) }.to_json elsif ci_service == :travis_pro if coverage_access_token.to_s.strip.length == 0 raise StandardError, "Access token is not set. Uploading coverage data for private repositories requires an access token." end { :service_job_id => travis_job_id, :service_name => "travis-pro", :repo_token => coverage_access_token, :source_files => coverage_files.map(&:as_json) }.to_json end else raise StandardError, "Environment variable `TRAVIS_JOB_ID` not set. Is this running on a travis build?" end elsif ci_service == :circleci if circleci_job_id coveralls_hash = { :service_job_id => circleci_job_id, :service_name => "circleci", :repo_token => coverage_access_token, :source_files => coverage_files.map(&:as_json), :git => circleci_git_info, :service_build_url => circleci_build_url } if circleci_pull_request != nil && circleci_pull_request.length > 0 coveralls_hash[:service_pull_request] = circleci_pull_request.split("/").last end coveralls_hash.to_json else raise StandardError, "Environment variable `CIRCLE_BUILD_NUM` not set. Is this running on a circleci build?" end elsif ci_service == :jenkins if jenkins_job_id { service_job_id: jenkins_job_id, service_name: "jenkins", repo_token: coverage_access_token, source_files: coverage_files.map(&:as_json), git: jenkins_git_info }.to_json else raise StandardError, "Environment variable `BUILD_ID` not set. Is this running on a jenkins build?" end elsif ci_service == :buildkite if buildkite_job_id { :service_job_id => buildkite_job_id, :service_name => "buildkite", :repo_token => coverage_access_token, :source_files => coverage_files.map(&:as_json), :git => buildkite_git_info, :service_build_url => buildkite_build_url, :service_pull_request => buildkite_pull_request }.to_json else raise StandardError, "Environment variable `BUILDKITE_BUILD_NUMBER` not set. Is this running on a buildkite build?" end elsif ci_service == :teamcity if teamcity_job_id { :service_job_id => teamcity_job_id, :service_name => "teamcity", :repo_token => coverage_access_token, :source_files => coverage_files.map(&:as_json), :git => teamcity_git_info }.to_json else raise StandardError, "Environment variable `TC_BUILD_NUMBER` not set. Is this running on a teamcity build?" end elsif ci_service == :github if coverage_access_token.to_s.strip.length == 0 raise StandardError, "Access token is not set. Uploading coverage data for private repositories requires an access token." end if github_job_id { :service_job_id => github_job_id, :service_name => "github", :repo_token => coverage_access_token, :repo_name => github_repo_name, :source_files => coverage_files.map(&:as_json), :service_build_url => github_build_url, :service_pull_request => github_pull_request, :git => github_git_info, :parallel => is_parallel, :flag_name => github_job_name }.to_json else raise StandardError, "Environment variable `GITHUB_RUN_ID` not set. Is this running on github build?" end elsif ci_service == :bitrise { :service_job_id => bitrise_job_id, :service_name => 'bitrise', :repo_token => coverage_access_token, :source_files => coverage_files.map(&:as_json), :service_pull_request => bitrise_pull_request, :service_branch => bitrise_branch_name, :git => bitrise_git_info }.to_json else { :service_job_id => ENV['CI_BUILD_NUMBER'], :service_name => ENV['CI_NAME'] || ci_service, :repo_token => coverage_access_token, :source_files => coverage_files.map(&:as_json), :service_build_url => ENV['CI_BUILD_URL'], :service_pull_request => ENV['CI_PULL_REQUEST'], :service_branch => ENV['CI_BRANCH'], :git => { :head => { :id => ENV['CI_COMMIT'], :committer_name => (`git log --format=%an -n 1 HEAD`.chomp || ""), :committer_email => (`git log --format=%ae -n 1 HEAD`.chomp || ""), :message => (`git log --format=%s -n 1 HEAD`.chomp || "") }, :branch => ENV['CI_BRANCH'] } }.to_json end end private :coveralls_coverage_data def post f = File.open('coveralls_json_file', 'w+') begin f.write(coveralls_coverage_data) f.close curl_result = `curl -s --form json_file=@#{f.path} #{coveralls_api_jobs_path}` if curl_result.is_a? String curl_result_json = JSON.parse(curl_result) if curl_result_json["error"] error_message = curl_result_json["message"] raise StandardError, "Error while uploading coverage data to Coveralls. CI Service: #{ci_service} Message: #{error_message}" end end rescue StandardError => e FileUtils.rm(f) raise e end FileUtils.rm(f) end def coveralls_api_jobs_path "https://coveralls.io/api/v1/jobs" end private :coveralls_api_jobs_path end end end ================================================ FILE: lib/slather/coverage_service/gutter_json_output.rb ================================================ module Slather module CoverageService module GutterJsonOutput def coverage_file_class if input_format == "profdata" Slather::ProfdataCoverageFile else Slather::CoverageFile end end private :coverage_file_class def post output = { 'meta' => { 'timestamp' => DateTime.now.strftime('%Y-%m-%d %H:%M:%S.%6N') } } symbols = {} coverage_files.each do |coverage_file| next unless coverage_file.raw_data filename = coverage_file.source_file_pathname.to_s filename = filename.sub(Pathname.pwd.to_s, '').reverse.chomp("/").reverse coverage_file.all_lines.each do |line| line_number = coverage_file.line_number_in_line(line) next unless line_number > 0 coverage = coverage_file.coverage_for_line(line) short_text = coverage != nil ? coverage.to_s : "-" symbol = { 'line' => line_number, 'long_text' => '', 'short_text' => short_text } if coverage != nil symbol['background_color'] = coverage.to_i > 0 ? '0x35CC4B' : '0xFC635E' end if symbols.has_key?(filename) symbols[filename] << symbol else symbols[filename] = [ symbol ] end end end output['symbols_by_file'] = symbols File.open('.gutter.json', 'w') { |file| file.write(output.to_json) } end end end end ================================================ FILE: lib/slather/coverage_service/hardcover.rb ================================================ module Slather module CoverageService module Hardcover def coverage_file_class if input_format == "profdata" Slather::ProfdataCoverageFile else Slather::CoverageFile end end private :coverage_file_class def jenkins_job_id "#{ENV['JOB_NAME']}/#{ENV['BUILD_NUMBER']}" end private :jenkins_job_id def hardcover_coverage_data if ci_service == :jenkins_ci if jenkins_job_id { :service_job_id => jenkins_job_id, :service_name => "jenkins-ci", :repo_token => Project.yml["hardcover_repo_token"], :source_files => coverage_files.map(&:as_json) }.to_json else raise StandardError, "Environment variables `BUILD_NUMBER` and `JOB_NAME` are not set. Is this running on a Jenkins build?" end else raise StandardError, "No support for ci named #{ci_service}" end end private :hardcover_coverage_data def post f = File.open('hardcover_json_file', 'w+') begin f.write(hardcover_coverage_data) f.close `curl --form json_file=@#{f.path} #{hardcover_api_jobs_path}` rescue StandardError => e FileUtils.rm(f) raise e end FileUtils.rm(f) end def hardcover_api_jobs_path "#{hardcover_base_url}/v1/jobs" end private :hardcover_api_jobs_path def hardcover_base_url url = Project.yml["hardcover_base_url"] unless url raise "No `hardcover_base_url` configured. Please add it to your `.slather.yml`" end url end private :hardcover_base_url end end end ================================================ FILE: lib/slather/coverage_service/html_output.rb ================================================ require 'nokogiri' require "cgi" module Slather module CoverageService module HtmlOutput attr_reader :docs def coverage_file_class if input_format == "profdata" Slather::ProfdataCoverageFile else Slather::CoverageFile end end private :coverage_file_class def directory_path is_path_valid = !output_directory.nil? && !output_directory.strip.eql?("") is_path_valid ? File.expand_path(output_directory) : "html" end private :directory_path def post create_html_reports(coverage_files) generate_reports(@docs) index_html_path = File.join(directory_path, "index.html") if show_html open_coverage index_html_path else print_path_coverage index_html_path end end def print_path_coverage(index_html) path = File.expand_path index_html puts "\nTo open the html reports, use \n\nopen '#{path}'\n\nor use '--show' flag to open it automatically.\n\n" end def open_coverage(index_html) path = File.expand_path index_html `open '#{path}'` if File.exist?(path) end def create_html_reports(coverage_files) create_index_html(coverage_files) create_htmls_from_files(coverage_files) end def generate_reports(reports) FileUtils.rm_rf(directory_path) if Dir.exist?(directory_path) FileUtils.mkdir_p(directory_path) FileUtils.cp(File.join(gem_root_path, "docs/logo.jpg"), directory_path) FileUtils.cp(File.join(gem_root_path, "assets/slather.css"), directory_path) FileUtils.cp(File.join(gem_root_path, "assets/highlight.pack.js"), directory_path) FileUtils.cp(File.join(gem_root_path, "assets/list.min.js"), directory_path) reports.each do |name, doc| html_file = File.join(directory_path, "#{name}.html") File.write(html_file, doc.to_html) end end def create_index_html(coverage_files) project_name = File.basename(self.xcodeproj) template = generate_html_template(project_name, true, false) total_relevant_lines = 0 total_tested_lines = 0 total_relevant_branches = 0 total_branches_tested = 0 coverage_files.each { |coverage_file| total_tested_lines += coverage_file.num_lines_tested total_relevant_lines += coverage_file.num_lines_testable total_relevant_branches += coverage_file.num_branches_testable total_branches_tested += coverage_file.num_branches_tested } builder = Nokogiri::HTML::Builder.with(template.at('#reports')) { |cov| cov.h2 "Files for \"#{project_name}\"" cov.h4 { percentage = (total_tested_lines / total_relevant_lines.to_f) * 100.0 cov.span "Total Coverage : " cov.span decimal_f(percentage) + '%', :class => class_for_coverage_percentage(percentage), :id => "total_coverage" cov.span " (" cov.span total_tested_lines, :id => "total_tested_lines" cov.span " of " cov.span total_relevant_lines, :id => "total_relevant_lines" cov.span " lines)" } cov.h4 { percentage = (total_branches_tested / total_relevant_branches.to_f) * 100.0 cov.span "Total Branch Coverage : " cov.span decimal_f(percentage) + '%', :class => class_for_coverage_percentage(percentage), :id => "total_coverage" cov.span " (" cov.span total_branches_tested, :id => "total_branches_tested" cov.span " of " cov.span total_relevant_branches, :id => "total_relevant_branches" cov.span " lines)" } cov.input(:class => "search", :placeholder => "Search") cov.table(:class => "coverage_list", :cellspacing => 0, :cellpadding => 0) { cov.thead { cov.tr { cov.th "%", :class => "col_num sort", "data-sort" => "data_percentage" cov.th "File", :class => "sort", "data-sort" => "data_filename" cov.th "Lines", :class => "col_percent sort", "data-sort" => "data_lines" cov.th "Relevant", :class => "col_percent sort", "data-sort" => "data_relevant" cov.th "Covered", :class => "col_percent sort", "data-sort" => "data_covered" cov.th "Missed", :class => "col_percent sort", "data-sort" => "data_missed" } } cov.tbody(:class => "list") { coverage_files.each { |coverage_file| filename = File.basename(coverage_file.source_file_pathname_relative_to_repo_root) filename_link = CGI.escape(filename) + ".html" cov.tr { percentage = coverage_file.percentage_lines_tested cov.td { cov.span decimal_f(percentage), :class => "percentage #{class_for_coverage_percentage(percentage)} data_percentage" } cov.td(:class => "data_filename") { cov.a filename, :href => filename_link } cov.td "#{coverage_file.line_coverage_data.count}", :class => "data_lines" cov.td "#{coverage_file.num_lines_testable}", :class => "data_relevant" cov.td "#{coverage_file.num_lines_tested}", :class => "data_covered" cov.td "#{(coverage_file.num_lines_testable - coverage_file.num_lines_tested)}", :class => "data_missed" } } } } } @docs = Hash.new @docs[:index] = builder.doc end def create_htmls_from_files(coverage_files) coverage_files.map { |file| create_html_from_file file } end def create_html_from_file(coverage_file) filepath = coverage_file.source_file_pathname_relative_to_repo_root filename = File.basename(filepath) percentage = coverage_file.percentage_lines_tested branch_percentage = coverage_file.rate_branches_tested * 100 cleaned_gcov_lines = coverage_file.cleaned_gcov_data.split("\n") is_file_empty = (cleaned_gcov_lines.count <= 0) template = generate_html_template(filename, false, is_file_empty) builder = Nokogiri::HTML::Builder.with(template.at('#reports')) { |cov| cov.h2(:class => "cov_title") { cov.span("Coverage for \"#{filename}\"" + (!is_file_empty ? " : " : "")) cov.span("Lines: ") unless is_file_empty cov.span("#{decimal_f(percentage)}%", :class => class_for_coverage_percentage(percentage)) unless is_file_empty cov.span(" Branches: ") unless is_file_empty cov.span("#{decimal_f(branch_percentage)}%", :class => class_for_coverage_percentage(branch_percentage)) unless is_file_empty } cov.h4("(#{coverage_file.num_lines_tested} of #{coverage_file.num_lines_testable} relevant lines covered)", :class => "cov_subtitle") cov.h4(filepath, :class => "cov_filepath") if is_file_empty cov.p "¯\\_(ツ)_/¯" next end line_number_separator = coverage_file.line_number_separator cov.table(:class => "source_code") { cleaned_gcov_lines.each do |line| line_number = coverage_file.line_number_in_line(line) missed_regions = coverage_file.branch_region_data[line_number] hits = coverage_file.coverage_for_line(line) next unless line_number > 0 line_source = line.split(line_number_separator, 3)[2] line_data = [line_number, line_source, hits_for_coverage_line(coverage_file, line)] classes = ["num", "src", "coverage"] cov.tr(:class => class_for_coverage_line(coverage_file,line)) { line_data.each_with_index { |line, idx| if idx != 1 cov.td(line, :class => classes[idx]) else cov.td(:class => classes[idx]) { cov.pre { # If the line has coverage and missed regions, split up # the line to show regions that weren't covered if missed_regions != nil && hits != nil && hits > 0 regions = missed_regions.map do |region| region_start, region_length = region if region_length != nil line[region_start, region_length] else line[region_start, line.length - region_start] end end current_line = line regions.each do |region| covered, remainder = current_line.split(region, 2) cov.code(covered, :class => "objc") cov.code(region, :class => "objc missed") current_line = remainder end cov.code(current_line, :class => "objc") else cov.code(line, :class => "objc") end } } end } } end } } @docs[filename] = builder.doc end def generate_html_template(title, is_index, is_file_empty) if cdn_assets logo_path = "https://cdn.jsdelivr.net/gh/SlatherOrg/slather/docs/logo.jpg" css_path = "https://cdn.jsdelivr.net/gh/SlatherOrg/slather/assets/slather.css" highlight_js_path = "https://cdn.jsdelivr.net/gh/SlatherOrg/slather/assets/highlight.pack.js" list_js_path = "https://cdn.jsdelivr.net/gh/SlatherOrg/slather/assets/list.min.js" else logo_path = "logo.jpg" css_path = "slather.css" highlight_js_path = "highlight.pack.js" list_js_path = "list.min.js" end builder = Nokogiri::HTML::Builder.new do |doc| doc.html { doc.head { doc.title "#{title} - Slather" doc.link :href => css_path, :media => "all", :rel => "stylesheet" } doc.body { doc.header { doc.div(:class => "row") { doc.a(:href => "index.html") { doc.img(:src => logo_path, :alt => "Slather logo") } } } doc.div(:class => "row") { doc.div(:id => "reports") } doc.footer { doc.div(:class => "row") { doc.p { doc.a("Fork me on Github", :href => "https://github.com/SlatherOrg/slather") } doc.p("© #{Date.today.year} Slather") } } if is_index doc.script :src => list_js_path doc.script "var reports = new List('reports', { valueNames: [ 'data_percentage', 'data_filename', 'data_lines', 'data_relevant', 'data_covered', 'data_missed' ]});" else unless is_file_empty doc.script :src => highlight_js_path doc.script "hljs.initHighlightingOnLoad();" end end } } end builder.doc end def gem_root_path File.expand_path File.join(File.dirname(__dir__), "../..") end def class_for_coverage_line(coverage_file, coverage_line) hits = coverage_file.coverage_for_line(coverage_line) case when hits == nil then "never" when hits > 0 then "covered" else "missed" end end def hits_for_coverage_line(coverage_file, coverage_line) hits = coverage_file.coverage_for_line(coverage_line) case when hits == nil then "" when hits > 0 then "#{hits}x" else "!" end end def class_for_coverage_percentage(percentage) case when percentage > 85 then "cov_high" when percentage > 70 then "cov_medium" else "cov_low" end end end end end ================================================ FILE: lib/slather/coverage_service/json_output.rb ================================================ require 'nokogiri' require 'date' module Slather module CoverageService module JsonOutput def coverage_file_class if input_format == "profdata" Slather::ProfdataCoverageFile else Slather::CoverageFile end end private :coverage_file_class def post report = coverage_files.map do |file| { file: file.source_file_pathname_relative_to_repo_root, coverage: file.line_coverage_data } end.to_json store_report(report) end def store_report(report) output_file = 'report.json' if output_directory FileUtils.mkdir_p(output_directory) output_file = File.join(output_directory, output_file) end File.write(output_file, report.to_s) end end end end ================================================ FILE: lib/slather/coverage_service/llvm_cov_output.rb ================================================ require 'nokogiri' require 'date' module Slather module CoverageService module LlvmCovOutput def coverage_file_class if input_format == "profdata" Slather::ProfdataCoverageFile else raise StandardError, "Only profdata input format supported by llvm-cov show." end end private :coverage_file_class def post report = coverage_files.map do |file| ["#{file.source_file_pathname.realpath}:", file.source_data, ""] end.flatten.join("\n") store_report(report) end def store_report(report) output_file = 'report.llcov' if output_directory FileUtils.mkdir_p(output_directory) output_file = File.join(output_directory, output_file) end File.write(output_file, report.to_s) end end end end ================================================ FILE: lib/slather/coverage_service/simple_output.rb ================================================ module Slather module CoverageService module SimpleOutput def coverage_file_class if input_format == "profdata" Slather::ProfdataCoverageFile else Slather::CoverageFile end end private :coverage_file_class def post total_project_lines = 0 total_project_lines_tested = 0 coverage_files.each do |coverage_file| # ignore lines that don't count towards coverage (comments, whitespace, etc). These are nil in the array. lines_tested = coverage_file.num_lines_tested total_lines = coverage_file.num_lines_testable percentage = decimal_f([coverage_file.percentage_lines_tested]) total_project_lines_tested += lines_tested total_project_lines += total_lines puts "#{coverage_file.source_file_pathname_relative_to_repo_root}: #{lines_tested} of #{total_lines} lines (#{percentage}%)" end # check if there needs to be custom reporting based on the ci service if ci_service == :teamcity # TeamCity Build Statistic Reporting # # Reporting format ##teamcity[buildStatisticValue key='' value=''] # key='CodeCoverageAbsLCovered' is total number of lines covered # key='CodeCoverageAbsLTotal' is total number of lines # # Sources: # - https://confluence.jetbrains.com/display/TCDL/Build+Script+Interaction+with+TeamCity#BuildScriptInteractionwithTeamCity-ReportingBuildStatistics # - https://confluence.jetbrains.com/display/TCDL/Custom+Chart#CustomChart-listOfDefaultStatisticValues puts "##teamcity[buildStatisticValue key='CodeCoverageAbsLCovered' value='%i']" % total_project_lines_tested puts "##teamcity[buildStatisticValue key='CodeCoverageAbsLTotal' value='%i']" % total_project_lines end total_percentage = decimal_f([(total_project_lines_tested / total_project_lines.to_f) * 100.0]) puts "Tested #{total_project_lines_tested}/#{total_project_lines} statements" puts "Test Coverage: #{total_percentage}%" end end end end ================================================ FILE: lib/slather/coverage_service/sonarqube_xml_output.rb ================================================ require 'nokogiri' require 'date' module Slather module CoverageService module SonarqubeXmlOutput def coverage_file_class if input_format == "profdata" Slather::ProfdataCoverageFile else Slather::CoverageFile end end private :coverage_file_class def post cobertura_xml_report = create_xml_report(coverage_files) store_report(cobertura_xml_report) end def store_report(report) output_file = 'sonarqube-generic-coverage.xml' if output_directory FileUtils.mkdir_p(output_directory) output_file = File.join(output_directory, output_file) end File.write(output_file, report.to_s) end def create_xml_report(coverage_files) create_empty_xml_report coverage_node = @doc.root coverage_node['version'] = "1" coverage_files.each do |coverage_file| file_node = Nokogiri::XML::Node.new "file", @doc file_node.parent = coverage_node file_node['path'] = coverage_file.source_file_pathname_relative_to_repo_root.to_s coverage_file.all_lines.each do |line| if coverage_file.coverage_for_line(line) line_node = Nokogiri::XML::Node.new "lineToCover", @doc line_node['lineNumber'] = coverage_file.line_number_in_line(line) line_node['covered'] = coverage_file.coverage_for_line(line) == 0 ? "false" : "true" line_node.parent = file_node end end end @doc.to_xml end def create_empty_xml_report builder = Nokogiri::XML::Builder.new do |xml| xml.coverage end @doc = builder.doc end end end end ================================================ FILE: lib/slather/coveralls_coverage.rb ================================================ module Slather module CoverallsCoverage def as_json { :name => source_file_pathname_relative_to_repo_root.to_s, :source => source_data, :coverage => line_coverage_data } end end end ================================================ FILE: lib/slather/profdata_coverage_file.rb ================================================ require_relative 'coverage_info' require_relative 'coveralls_coverage' require 'digest/md5' module Slather class ProfdataCoverageFile include CoverageInfo include CoverallsCoverage attr_accessor :project, :source, :segments, :line_numbers_first, :line_data def initialize(project, source, line_numbers_first) self.project = project self.source = source self.line_numbers_first = line_numbers_first create_line_data end def create_line_data line_data = Hash.new all_lines.each { |line| line_data[line_number_in_line(line, self.line_numbers_first)] = line } self.line_data = line_data end private :create_line_data def path_on_first_line? !source.lstrip.start_with?("1|") end def source_file_pathname @source_file_pathname ||= begin if path_on_first_line? end_index = self.source.index(/:?\n/) if end_index != nil end_index -= 1 path = self.source[0..end_index] else # Empty file, output just contains path path = self.source.sub ":", "" end path &&= Pathname(path) else # llvm-cov was run with just one matching source file # It doesn't print the source path in this case, so we have to find it ourselves # This is slow because we read every source file and compare it, but this should only happen if there aren't many source files digest = Digest::MD5.digest(self.raw_source) path = nil project.find_source_files.each do |file| file_digest = Digest::MD5.digest(File.read(file).strip) if digest == file_digest path = file end end path end end end def source_file_pathname= (source_file_pathname) @source_file_pathname = source_file_pathname end def source_file File.new(source_file_pathname) end def source_code_lines lines = self.source.split("\n")[(path_on_first_line? ? 1 : 0)..-1] ignore_error_lines(lines) end def ignore_error_lines(lines, line_numbers_first = self.line_numbers_first) if line_numbers_first lines.reject { |line| line.lstrip.start_with?('|', '--') } else lines end end def source_data all_lines.join("\n") end def all_lines @all_lines ||= source_code_lines end def raw_source self.source.lines.map do |line| line.split('|').last end.join end def cleaned_gcov_data source_data end def raw_data self.source end def line_number_in_line(line, line_numbers_first = self.line_numbers_first) if line_numbers_first # Skip regex if the number is the first thing in the line fastpath_number = line.to_i return fastpath_number if fastpath_number != 0 line =~ /^(\s*)(\d*)/ group = $2 else line =~ /^(\s*)(\d*)\|(\s*)(\d+)\|/ group = $4 end if group != nil match = group.strip case match when /[0-9]+/ return match.to_i end else # llvm-cov outputs hit counts as 25.3k or 3.8M, so check this pattern as well did_match = line =~ /^(\s*)(\d+\.\d+)(k|M)\|(\s*)(\d+)\|/ if did_match match = $5.strip case match when /[0-9]+/ return match.to_i end end end 0 end def line_coverage_data all_lines.map do |line| coverage_for_line(line, self.line_numbers_first) end end def coverage_for_line(line, line_numbers_first = self.line_numbers_first) line = line.gsub(":", "|") if line_numbers_first line =~ /^(\s*)(\d*)\|(\s*)(\d+)\|/ group = $4 else line =~ /^(\s*)(\d*)\|/ group = $2 end if group == nil # Check for thousands or millions (llvm-cov outputs hit counts as 25.3k or 3.8M) if line_numbers_first did_match = line =~ /^(\s*)(\d+)\|(\s*)(\d+\.\d+)(k|M)\|/ group = $4 units_group = $5 else did_match = line =~ /^(\s*)(\d+\.\d+)(k|M)\|/ group = $2 units_group = $3 end if did_match count = group.strip units = units_group == 'k' ? 1000 : 1000000 (count.to_f * units).to_i else return nil end else match = group.strip case match when /[0-9]+/ match.to_i when /#+/ 0 when "-" nil end end end def branch_coverage_data @branch_coverage_data ||= begin branch_coverage_data = Hash.new self.segments.each do |segment| line, col, hits, has_count, *rest = segment next if !has_count if branch_coverage_data.key?(line) branch_coverage_data[line] = branch_coverage_data[line] + [hits] else branch_coverage_data[line] = [hits] end end branch_coverage_data end end def branch_region_data @branch_region_data ||= begin branch_region_data = Hash.new region_start = nil current_line = 0 @segments ||= [] @segments.each do |segment| line, col, hits, has_count, *rest = segment # Make column 0 based index col = col - 1 if hits == 0 && has_count current_line = line region_start = col elsif region_start != nil && hits > 0 && has_count # if the region wrapped to a new line before ending, put nil to indicate it didnt end on this line region_end = line == current_line ? col - region_start : nil if branch_region_data.key?(current_line) branch_region_data[current_line] << [region_start, region_end] else branch_region_data[current_line] = [[region_start, region_end]] end region_start = nil end end branch_region_data end end def source_file_basename File.basename(source_file_pathname, '.swift') end def line_number_separator "|" end def supported_file_extensions ["swift"] end private :supported_file_extensions def ignored? path = source_file_pathname.to_s # This indicates a llvm-cov coverage warning (occurs if a passed in source file # is not covered or with cache in some cases). return true if path.end_with?("isn't covered.") # Ignore source files inside of platform SDKs return true if path.include?("/Xcode.*\.app\/Contents\/Developer\/Platforms") super end end end ================================================ FILE: lib/slather/project.rb ================================================ require 'fileutils' require 'xcodeproj' require 'json' require 'yaml' require 'shellwords' module Xcodeproj class Project def slather_setup_for_coverage(format = :auto) unless [:gcov, :clang, :auto].include?(format) raise StandardError, "Only supported formats for setup are gcov, clang or auto" end if format == :auto format = Slather.xcode_version[0] < 7 ? :gcov : :clang end build_configurations.each do |build_configuration| if format == :clang build_configuration.build_settings["CLANG_ENABLE_CODE_COVERAGE"] = "YES" else build_configuration.build_settings["GCC_INSTRUMENT_PROGRAM_FLOW_ARCS"] = "YES" build_configuration.build_settings["GCC_GENERATE_TEST_COVERAGE_FILES"] = "YES" end end # Patch xcschemes too if format == :clang schemes_path = Xcodeproj::XCScheme.shared_data_dir(self.path) Xcodeproj::Project.schemes(self.path).each do |scheme_name| xcscheme_path = "#{schemes_path + scheme_name}.xcscheme" xcscheme = Xcodeproj::XCScheme.new(xcscheme_path) xcscheme.test_action.xml_element.attributes['codeCoverageEnabled'] = 'YES' xcscheme.save_as(self.path, scheme_name) end end end end end module Slather class Project < Xcodeproj::Project attr_accessor :build_directory, :ignore_list, :ci_service, :coverage_service, :coverage_access_token, :source_directory, :output_directory, :xcodeproj, :show_html, :cdn_assets, :verbose_mode, :input_format, :scheme, :workspace, :binary_file, :binary_basename, :arch, :source_files, :decimals, :llvm_version, :configuration alias_method :setup_for_coverage, :slather_setup_for_coverage def self.open(xcodeproj) proj = super proj.xcodeproj = xcodeproj proj end def failure_help_string "\n\tAre you sure your project is generating coverage? Make sure you enable code coverage in the Test section of your Xcode scheme.\n\tDid you specify your Xcode scheme? (--scheme or 'scheme' in .slather.yml)\n\tIf you're using a workspace, did you specify it? (--workspace or 'workspace' in .slather.yml)\n\tIf you use a different Xcode configuration, did you specify it? (--configuration or 'configuration' in .slather.yml)" end def derived_data_path # Get the derived data path from xcodebuild # Use OBJROOT when possible, as it provides regardless of whether or not the Derived Data location is customized if self.workspace projectOrWorkspaceArgument = "-workspace \"#{self.workspace}\"" else projectOrWorkspaceArgument = "-project \"#{self.path}\"" end if self.scheme schemeArgument = "-scheme \"#{self.scheme}\"" buildAction = "test" else schemeArgument = nil buildAction = nil end # redirect stderr to avoid xcodebuild errors being printed. build_settings = `xcodebuild #{projectOrWorkspaceArgument} #{schemeArgument} -showBuildSettings #{buildAction} CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO 2>&1` if build_settings derived_data_path = build_settings.match(/ OBJROOT = (.+)/) # when match fails derived_data_path is nil derived_data_path = derived_data_path[1] if derived_data_path end if derived_data_path == nil derived_data_path = File.expand_path('~') + "/Library/Developer/Xcode/DerivedData/" end derived_data_path end private :derived_data_path def coverage_files if self.input_format == "profdata" profdata_coverage_files else gcov_coverage_files end end def gcov_coverage_files coverage_files = Dir["#{build_directory}/**/*.gcno"].map do |file| coverage_file = coverage_file_class.new(self, file) # If there's no source file for this gcno, it probably belongs to another project. coverage_file.source_file_pathname && !coverage_file.ignored? ? coverage_file : nil end.compact if coverage_files.empty? raise StandardError, "No coverage files found." else dedupe(coverage_files) end end private :gcov_coverage_files def profdata_coverage_files coverage_files = [] if self.binary_file self.binary_file.each do |binary_path| pathnames_per_binary = pathnames_per_binary(binary_path) coverage_files.concat(create_coverage_files_for_binary(binary_path, pathnames_per_binary)) end end if coverage_files.empty? raise StandardError, "No coverage files found." else dedupe(coverage_files) end end private :profdata_coverage_files def pathnames_per_binary(binary_path) coverage_json_string = llvm_cov_export_output(binary_path) if coverage_json_string.strip != "" # JSON.parse will crash on an empty string, so just skip everything if the string is empty. coverage_json = JSON.parse(coverage_json_string) coverage_json["data"].reduce([]) do |result, chunk| result.concat(chunk["files"].map do |file| filename = file["filename"] path = Pathname(filename) # Don't crash if the file doesn't exist on disk. # This may happen for autogenerated files that have been deleted. filename = path.exist? ? path.realpath : filename {"filename" => filename, "segments" => file["segments"]} end) end end end private :pathnames_per_binary def create_coverage_files_for_binary(binary_path, pathnames_per_binary) return [] unless pathnames_per_binary != nil coverage_files = [] begin coverage_files.concat(create_coverage_files(binary_path, pathnames_per_binary)) rescue Errno::E2BIG => e # pathnames_per_binary is too big for the OS to handle so it's split in two halfs which are processed independently if pathnames_per_binary.count > 1 left, right = pathnames_per_binary.each_slice( (pathnames_per_binary.size/2.0).round ).to_a coverage_files.concat(create_coverage_files_for_binary(binary_path, left)) coverage_files.concat(create_coverage_files_for_binary(binary_path, right)) else # pathnames_per_binary contains one element which is too big for the OS to handle. raise e, "#{e}. A path in your project is close to the E2BIG limit. https://github.com/SlatherOrg/slather/pull/414", e.backtrace end end coverage_files end private :create_coverage_files_for_binary def create_coverage_files(binary_path, path_objects) line_numbers_first = Gem::Version.new(self.llvm_version) >= Gem::Version.new('8.1.0') # get just file names from the path objects pathnames = path_objects.map { |path_obj| path_obj["filename"] }.compact # Map of path name => segment array paths_to_segments = path_objects.reduce(Hash.new) do |hash, path_obj| hash[path_obj["filename"]] = path_obj["segments"] hash end files = create_profdata(binary_path, pathnames) files.map do |source| coverage_file = coverage_file_class.new(self, source, line_numbers_first) # If a single source file is used, the resulting output does not contain the file name. coverage_file.source_file_pathname = pathnames.first if pathnames.count == 1 # if there is segment data for the given path, add it to the coverage_file if paths_to_segments.key?(coverage_file.source_file_pathname) coverage_file.segments = paths_to_segments[coverage_file.source_file_pathname] end coverage_file.source_file_pathname && !coverage_file.ignored? && coverage_file.include_file? ? coverage_file : nil end.compact end private :create_coverage_files def create_profdata(binary_path, pathnames) profdata_llvm_cov_output(binary_path, pathnames).split("\n\n") end private :create_profdata def remove_extension(path) path.split(".")[0..-2].join(".") end def first_product_name first_product = self.products.first # If name is not available it computes it using # the path by dropping the 'extension' of the path. first_product.name || remove_extension(first_product.path) end def profdata_coverage_dir @profdata_coverage_dir ||= begin raise StandardError, "The specified build directory (#{self.build_directory}) does not exist" unless File.exist?(self.build_directory) dir = nil if self.scheme dir = Dir[File.join(build_directory,"/**/CodeCoverage/#{self.scheme}")].first else dir = Dir[File.join(build_directory,"/**/#{first_product_name}")].first end if dir == nil # Xcode 7.3 moved the location of Coverage.profdata dir = Dir[File.join(build_directory,"/**/CodeCoverage")].first end if dir == nil && Slather.xcode_version[0] >= 9 # Xcode 9 moved the location of Coverage.profdata coverage_files = Dir[File.join(build_directory, "/**/ProfileData/*/Coverage.profdata")] if coverage_files.count == 0 # Look up one directory # The ProfileData directory is next to Intermediates.noindex (in previous versions of Xcode the coverage was inside Intermediates) coverage_files = Dir[File.join(build_directory, "../**/ProfileData/*/Coverage.profdata")] end if coverage_files != nil && coverage_files.count != 0 dir = Pathname.new(coverage_files.first).parent() end end raise StandardError, "No coverage directory found." unless dir != nil dir end end def profdata_file profdata_coverage_dir = self.profdata_coverage_dir if profdata_coverage_dir == nil raise StandardError, "No coverage directory found. Please make sure the \"Code Coverage\" checkbox is enabled in your scheme's Test action or the build_directory property is set." end file = Dir["#{profdata_coverage_dir}/**/Coverage.profdata"].first unless file != nil return nil end return File.expand_path(file) end private :profdata_file def unsafe_llvm_cov_export_output(binary_path) profdata_file_arg = profdata_file if profdata_file_arg == nil raise StandardError, "No Coverage.profdata files found. Please make sure the \"Code Coverage\" checkbox is enabled in your scheme's Test action or the build_directory property is set." end if binary_path == nil raise StandardError, "No binary file found." end llvm_cov_args = %W(export -instr-profile #{profdata_file_arg} #{binary_path}) if self.arch llvm_cov_args << "--arch" << self.arch end `xcrun llvm-cov #{llvm_cov_args.shelljoin}` end private :unsafe_llvm_cov_export_output def llvm_cov_export_output(binary_path) output = unsafe_llvm_cov_export_output(binary_path) output.valid_encoding? ? output : output.encode!('UTF-8', 'binary', :invalid => :replace, undef: :replace) end private :llvm_cov_export_output def unsafe_profdata_llvm_cov_output(binary_path, source_files) profdata_file_arg = profdata_file if profdata_file_arg == nil raise StandardError, "No Coverage.profdata files found. Please make sure the \"Code Coverage\" checkbox is enabled in your scheme's Test action or the build_directory property is set." end if binary_path == nil raise StandardError, "No binary file found." end llvm_cov_args = %W(show -instr-profile #{profdata_file_arg} #{binary_path}) if self.arch llvm_cov_args << "--arch" << self.arch end # POSIX systems have an ARG_MAX for the maximum total length of the command line, so the command may fail with an error message of "Argument list too long". # Using the xargs command we can break the list of source_files into sublists small enough to be acceptable. `printf '%s\\0' #{source_files.shelljoin} | xargs -0 xcrun llvm-cov #{llvm_cov_args.shelljoin}` end private :unsafe_profdata_llvm_cov_output def profdata_llvm_cov_output(binary_path, source_files) output = unsafe_profdata_llvm_cov_output(binary_path, source_files) output.valid_encoding? ? output : output.encode!('UTF-8', 'binary', :invalid => :replace, undef: :replace) end private :profdata_llvm_cov_output def dedupe(coverage_files) coverage_files.group_by(&:source_file_pathname).values.map { |cf_array| cf_array.max_by(&:percentage_lines_tested) } end private :dedupe @@ymlfile = '.slather.yml' def self.yml_filename=(var) @@ymlfile = var end def self.yml_filename @@ymlfile end def self.yml @yml ||= File.exist?(yml_filename) ? YAML.load_file(yml_filename) : {} end def configure begin configure_scheme configure_configuration configure_workspace configure_build_directory configure_ignore_list configure_ci_service configure_coverage_access_token configure_coverage_service configure_source_directory configure_output_directory configure_input_format configure_arch configure_binary_file configure_decimals configure_source_files self.llvm_version = `xcrun llvm-cov --version`.match(/LLVM version ([\d\.]+)/).captures[0] rescue => e puts e.message puts failure_help_string puts "\n" raise end if self.verbose_mode puts "\nProcessing coverage file: #{profdata_file}" if self.binary_file puts "Against binary files:" self.binary_file.each do |binary_file| puts "\t#{binary_file}" end else puts "No binary files found." end puts "\n" end end def configure_build_directory self.build_directory ||= self.class.yml["build_directory"] || derived_data_path end def configure_source_directory self.source_directory ||= self.class.yml["source_directory"] if self.class.yml["source_directory"] end def configure_output_directory self.output_directory ||= self.class.yml["output_directory"] if self.class.yml["output_directory"] end def configure_ignore_list self.ignore_list ||= [(self.class.yml["ignore"] || [])].flatten end def configure_source_files self.source_files ||= [(self.class.yml["source_files"] || [])].flatten end def configure_ci_service self.ci_service ||= (ENV["CI_SERVICE"] || self.class.yml["ci_service"] || :other) end def configure_input_format self.input_format ||= (self.class.yml["input_format"] || "auto") end def input_format=(format) format ||= "auto" unless %w(gcov profdata auto).include?(format) raise StandardError, "Only supported input formats are gcov, profdata or auto" end if format == "auto" @input_format = Slather.xcode_version[0] < 7 ? "gcov" : "profdata" else @input_format = format end end def configure_scheme self.scheme ||= self.class.yml["scheme"] if self.class.yml["scheme"] end def configure_configuration self.configuration ||= self.class.yml["configuration"] if self.class.yml["configuration"] end def configure_decimals return if self.decimals self.decimals ||= self.class.yml["decimals"] if self.class.yml["decimals"] self.decimals = self.decimals ? Integer(self.decimals) : 2 end def configure_workspace self.workspace ||= self.class.yml["workspace"] if self.class.yml["workspace"] end def ci_service=(service) @ci_service = service && service.to_sym end def configure_coverage_service self.coverage_service ||= (self.class.yml["coverage_service"] || :terminal) end def configure_coverage_access_token self.coverage_access_token ||= (ENV["COVERAGE_ACCESS_TOKEN"] || self.class.yml["coverage_access_token"] || "") end def coverage_service=(service) service = service && service.to_sym case service when :coveralls extend(Slather::CoverageService::Coveralls) when :hardcover extend(Slather::CoverageService::Hardcover) when :terminal extend(Slather::CoverageService::SimpleOutput) when :gutter_json extend(Slather::CoverageService::GutterJsonOutput) when :cobertura_xml extend(Slather::CoverageService::CoberturaXmlOutput) when :llvm_cov extend(Slather::CoverageService::LlvmCovOutput) when :html extend(Slather::CoverageService::HtmlOutput) when :json extend(Slather::CoverageService::JsonOutput) when :sonarqube_xml extend(Slather::CoverageService::SonarqubeXmlOutput) else raise ArgumentError, "`#{coverage_service}` is not a valid coverage service. Try `terminal`, `coveralls`, `gutter_json`, `cobertura_xml` or `html`" end @coverage_service = service end def configure_binary_file if self.input_format == "profdata" self.binary_file = load_option_array("binary_file") || find_binary_files end end def configure_arch self.arch ||= self.class.yml["arch"] if self.class.yml["arch"] end def decimal_f decimal_arg configure_decimals unless decimals decimal = "%.#{decimals}f" % decimal_arg return decimal if decimals == 2 # special case 2 for backwards compatibility decimal.to_f.to_s end def find_binary_file_in_bundle(bundle_file) if File.directory? bundle_file bundle_file_noext = File.basename(bundle_file, File.extname(bundle_file)) # Search for .debug.dylib binaries # See https://developer.apple.com/documentation/xcode/build-settings-reference#Enable-Debug-Dylib-Support for details debug_dylib_matches = Dir["#{bundle_file}/**/#{bundle_file_noext}.debug.dylib"] if debug_dylib_matches.length() > 0 debug_dylib_matches.first else Dir["#{bundle_file}/**/#{bundle_file_noext}"].first end else bundle_file end end def find_binary_files binary_basename = load_option_array("binary_basename") found_binaries = [] # Get scheme info out of the xcodeproj if self.scheme schemes_path = Xcodeproj::XCScheme.shared_data_dir(self.path) xcscheme_path = "#{schemes_path + self.scheme}.xcscheme" # Try to look inside 'xcuserdata' if the scheme is not found in 'xcshareddata' if !File.file?(xcscheme_path) schemes_path = Xcodeproj::XCScheme.user_data_dir(self.path) xcscheme_path = "#{schemes_path + self.scheme}.xcscheme" end if self.workspace and !File.file?(xcscheme_path) # No scheme was found in the xcodeproj, check the workspace schemes_path = Xcodeproj::XCScheme.shared_data_dir(self.workspace) xcscheme_path = "#{schemes_path + self.scheme}.xcscheme" if !File.file?(xcscheme_path) schemes_path = Xcodeproj::XCScheme.user_data_dir(self.workspace) xcscheme_path = "#{schemes_path + self.scheme}.xcscheme" end end raise StandardError, "No scheme named '#{self.scheme}' found in #{self.path}" unless File.exist? xcscheme_path xcscheme = Xcodeproj::XCScheme.new(xcscheme_path) if self.configuration configuration = self.configuration else configuration = xcscheme.test_action.build_configuration end search_list = binary_basename || find_buildable_names(xcscheme) search_dir = profdata_coverage_dir if Slather.xcode_version[0] >= 9 # Go from the directory containing Coverage.profdata back to the directory containing Products (back out of ProfileData/UUID-dir) search_dir = File.join(search_dir, '../..') end search_list.each do |search_for| found_product = Dir["#{search_dir}/Products/#{configuration}*/#{search_for}*"].sort { |x, y| # Sort the matches without the file extension to ensure better matches when there are multiple candidates # For example, if the binary_basename is Test then we want Test.app to be matched before Test Helper.app File.basename(x, File.extname(x)) <=> File.basename(y, File.extname(y)) }.find { |path| next if path.end_with? ".dSYM" next if path.end_with? ".swiftmodule" if File.directory? path path = find_binary_file_in_bundle(path) next if path.nil? end matches_arch(path) } if found_product and File.directory? found_product found_binary = find_binary_file_in_bundle(found_product) else found_binary = found_product end if found_binary found_binaries.push(found_binary) end end else xctest_bundle = Dir["#{profdata_coverage_dir}/**/*.xctest"].reject { |bundle| # Ignore xctest bundles that are in the UI runner app bundle.include? "-Runner.app/PlugIns/" }.first # Find the matching binary file search_list = binary_basename || ['*'] search_list.each do |search_for| xctest_bundle_file_directory = Pathname.new(xctest_bundle).dirname app_bundle = Dir["#{xctest_bundle_file_directory}/#{search_for}.app"].first matched_xctest_bundle = Dir["#{xctest_bundle_file_directory}/#{search_for}.xctest"].first dynamic_lib_bundle = Dir["#{xctest_bundle_file_directory}/#{search_for}.{framework,dylib}"].first if app_bundle != nil found_binary = find_binary_file_in_bundle(app_bundle) elsif matched_xctest_bundle != nil found_binary = find_binary_file_in_bundle(matched_xctest_bundle) elsif dynamic_lib_bundle != nil found_binary = find_binary_file_in_bundle(dynamic_lib_bundle) else found_binary = find_binary_file_in_bundle(xctest_bundle) end if found_binary found_binaries.push(found_binary) end end end raise StandardError, "No product binary found in #{profdata_coverage_dir}." unless found_binaries.count > 0 found_binaries.map { |binary| File.expand_path(binary) } end def find_buildable_names(xcscheme) found_buildable_names = [] # enumerate code coverage targets begin code_coverage_targets = xcscheme.test_action.xml_element.elements['CodeCoverageTargets'] targets = code_coverage_targets.map do |node| Xcodeproj::XCScheme::BuildableReference.new(node) if node.is_a?(REXML::Element) end.compact buildable_names = targets.each do |target| found_buildable_names.push(target.buildable_name) end rescue # just in case if there are no entries in the test action end # enumerate build action entries begin xcscheme.build_action.entries.each do |entry| buildable_name = entry.buildable_references[0].buildable_name if !buildable_name.end_with? ".a" # Can't run code coverage on static libraries found_buildable_names.push(buildable_name) end end rescue # xcodeproj will raise an exception if there are no entries in the build action end # enumerate test action entries begin xcscheme.test_action.testables.each do |entry| buildable_name = entry.buildable_references[0].buildable_name found_buildable_names.push(buildable_name) end rescue # just in case if there are no entries in the test action end # some items are both buildable and testable, so return only unique ones found_buildable_names.uniq end def matches_arch(binary_path) if self.arch lipo_output = `lipo -info "#{binary_path}"` archs_in_binary = lipo_output.split(':').last.split(' ') archs_in_binary.include? self.arch else true end end def find_source_files source_files = load_option_array("source_files") return [] if source_files.nil? current_dir = Pathname("./").realpath paths = source_files.flat_map { |pattern| Dir.glob(pattern) }.uniq paths.map do |path| source_file_absolute_path = Pathname(path).realpath source_file_relative_path = source_file_absolute_path.relative_path_from(current_dir) self.ignore_list.any? { |ignore| File.fnmatch(ignore, source_file_relative_path) } ? nil : source_file_absolute_path end.compact end def load_option_array(option) value = self.send(option.to_sym) # Only load if a value is not already set if !value value_yml = self.class.yml[option] # Need to check the type in the config file because it can be a string or array if value_yml and value_yml.is_a? Array value = value_yml elsif value_yml value = [value_yml] end end value end end end ================================================ FILE: lib/slather/version.rb ================================================ module Slather VERSION = '2.8.5' unless defined?(Slather::VERSION) end ================================================ FILE: lib/slather.rb ================================================ require_relative 'slather/version' require_relative 'slather/project' require_relative 'slather/coverage_info' require_relative 'slather/coverage_file' require_relative 'slather/coveralls_coverage' require_relative 'slather/profdata_coverage_file' require_relative 'slather/coverage_service/cobertura_xml_output' require_relative 'slather/coverage_service/coveralls' require_relative 'slather/coverage_service/hardcover' require_relative 'slather/coverage_service/gutter_json_output' require_relative 'slather/coverage_service/simple_output' require_relative 'slather/coverage_service/html_output' require_relative 'slather/coverage_service/json_output' require_relative 'slather/coverage_service/llvm_cov_output' require_relative 'slather/coverage_service/sonarqube_xml_output' require 'cfpropertylist' module Slather Encoding.default_external = "utf-8" def self.prepare_pods(pods) Pod::UI.warn("[Slather] prepare_pods is now deprecated. The call to prepare_pods in your Podfile can simply be ommitted.") end def self.xcode_version xcode_path = `xcode-select -p`.strip plist = CFPropertyList::List.new(:file => File.join(xcode_path, '..', 'Info.plist')) xcode_version = CFPropertyList.native_types(plist.value)["CFBundleShortVersionString"] xcode_version.split('.').map(&:to_i) end end ================================================ FILE: slather.gemspec ================================================ # encoding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'slather/version' Gem::Specification.new do |spec| spec.name = 'slather' spec.version = Slather::VERSION spec.authors = ['Mark Larsen'] spec.email = ['mark@venmo.com'] spec.summary = %q{Test coverage reports for Xcode projects} spec.homepage = 'https://github.com/SlatherOrg/slather' spec.license = 'MIT' spec.files = `git ls-files -z`.force_encoding('utf-8').split("\x0") spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ['lib'] spec.add_development_dependency 'bundler', '~> 2.0' spec.add_development_dependency 'coveralls', '~> 0.8' spec.add_development_dependency 'simplecov', '~> 0' spec.add_development_dependency 'rake', '~> 12.3' spec.add_development_dependency 'rspec', '~> 3.8' spec.add_development_dependency 'pry', '~> 0.12' spec.add_development_dependency 'cocoapods', '~> 1.10.beta.1' spec.add_development_dependency 'json_spec', '~> 1.1' spec.add_development_dependency 'equivalent-xml', '~> 0.6' spec.add_dependency 'clamp', '~> 1.3' spec.add_dependency 'xcodeproj', '~> 1.27' spec.add_dependency 'nokogiri', '>= 1.14.3' spec.add_dependency 'CFPropertyList', '>= 2.2', '< 4' spec.add_runtime_dependency 'activesupport' end ================================================ FILE: spec/fixtures/FixtureFramework/FixtureFramework.h ================================================ // // FixtureFramework.h // FixtureFramework // // Created by Stephen Williams on 11/03/21. // Copyright © 2021 marklarr. All rights reserved. // #import //! Project version number for FixtureFramework. FOUNDATION_EXPORT double FixtureFrameworkVersionNumber; //! Project version string for FixtureFramework. FOUNDATION_EXPORT const unsigned char FixtureFrameworkVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import ================================================ FILE: spec/fixtures/FixtureFramework/FlashExperiment.swift ================================================ import Foundation public struct FlashExperiment { public let isAwesome = true public init() {} } ================================================ FILE: spec/fixtures/FixtureFramework/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString 1.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSHumanReadableCopyright Copyright © 2021 marklarr. All rights reserved. ================================================ FILE: spec/fixtures/FixtureFrameworkTests/FixtureFrameworkTests.swift ================================================ // // FixtureFrameworkTests.swift // FixtureFrameworkTests // // Created by Stephen Williams on 11/03/21. // Copyright © 2021 marklarr. All rights reserved. // import XCTest @testable import FixtureFramework class FixtureFrameworkTests: XCTestCase { override func setUpWithError() throws { // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDownWithError() throws { // Put teardown code here. This method is called after the invocation of each test method in the class. } func testExample() throws { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. } func testPerformanceExample() throws { // This is an example of a performance test case. self.measure { // Put the code you want to measure the time of here. } } } ================================================ FILE: spec/fixtures/FixtureFrameworkTests/FlashExperimentTests.swift ================================================ import XCTest import FixtureFramework class FlashExperimentTests: XCTestCase { func testExample() throws { let sut = FlashExperiment() XCTAssertTrue(sut.isAwesome, "Your flash experiment isn't awesome!") } } ================================================ FILE: spec/fixtures/FixtureFrameworkTests/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString 1.0 CFBundleVersion 1 ================================================ FILE: spec/fixtures/fixtures/Fixtures.swift ================================================ import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { return true } func applicationWillResignActive(application: UIApplication) { } } ================================================ FILE: spec/fixtures/fixtures/Supporting Files/fixtures-Prefix.pch ================================================ // // Prefix header // // The contents of this file are implicitly included at the beginning of every source file. // #ifdef __OBJC__ #import #endif ================================================ FILE: spec/fixtures/fixtures/fixtures.h ================================================ // // fixtures.h // fixtures // // Created by Mark Larsen on 6/24/14. // Copyright (c) 2014 marklarr. All rights reserved. // #import @interface fixtures : NSObject - (void)testedMethod; - (void)untestedMethod; @end ================================================ FILE: spec/fixtures/fixtures/fixtures.m ================================================ // // fixtures.m // fixtures // // Created by Mark Larsen on 6/24/14. // Copyright (c) 2014 marklarr 🌟. All rights reserved. // #import "fixtures.h" @implementation fixtures - (void)testedMethod { NSLog(@"tested"); } - (void)untestedMethod { NSLog(@"untested"); } @end ================================================ FILE: spec/fixtures/fixtures/fixtures_cpp.cpp ================================================ // // fixtures_cpp.cpp // fixtures // // Created by Larsen, Mark on 4/3/15. // Copyright (c) 2015 marklarr. All rights reserved. // #include "fixtures_cpp.h" ================================================ FILE: spec/fixtures/fixtures/fixtures_cpp.h ================================================ #ifndef __fixtures__fixtures_cpp__ #define __fixtures__fixtures_cpp__ #include #endif /* defined(__fixtures__fixtures_cpp__) */ ================================================ FILE: spec/fixtures/fixtures/fixtures_m.h ================================================ #import @interface fixtures_m : NSObject @end ================================================ FILE: spec/fixtures/fixtures/fixtures_m.m ================================================ #import "fixtures_m.h" @implementation fixtures_m @end ================================================ FILE: spec/fixtures/fixtures/fixtures_mm.h ================================================ #import @interface fixtures_mm : NSObject @end ================================================ FILE: spec/fixtures/fixtures/fixtures_mm.mm ================================================ #import "fixtures_mm.h" @implementation fixtures_mm @end ================================================ FILE: spec/fixtures/fixtures/more_files/Branches.h ================================================ // // Branches.h // fixtures // // Created by Julian Krumow on 11.10.14. // Copyright (c) 2014 marklarr. All rights reserved. // #import @interface Branches : NSObject - (void)branches:(BOOL)goIf skipBranches:(BOOL)skipBranches; @end ================================================ FILE: spec/fixtures/fixtures/more_files/Branches.m ================================================ // // Branches.m // fixtures // // Created by Julian Krumow on 11.10.14. // Copyright (c) 2014 marklarr. All rights reserved. // #import "Branches.h" @implementation Branches - (void)branches:(BOOL)goIf skipBranches:(BOOL)skipBranches { if (goIf) { NSLog(@"foo."); if (!skipBranches) { NSLog(@"not skipped."); } } else { NSLog(@"bar."); } int i = 5; if (i == 5) { return; } switch (i) { case 0: NSLog(@"0"); break; case 1: NSLog(@"1"); break; case 5: NSLog(@"5"); break; default: break; } } @end ================================================ FILE: spec/fixtures/fixtures/more_files/Empty.h ================================================ // // Empty.h // fixtures // // Created by Julian Krumow on 27.10.14. // Copyright (c) 2014 marklarr. All rights reserved. // #import @interface Empty : NSObject @end ================================================ FILE: spec/fixtures/fixtures/more_files/Empty.m ================================================ // // Empty.m // fixtures // // Created by Julian Krumow on 27.10.14. // Copyright (c) 2014 marklarr. All rights reserved. // #import "Empty.h" @implementation Empty @end ================================================ FILE: spec/fixtures/fixtures/more_files/peekaview.h ================================================ // // peekaview.h // fixtures // // Created by Mark Larsen on 6/25/14. // Copyright (c) 2014 marklarr. All rights reserved. // #import @interface peekaview : NSView @end ================================================ FILE: spec/fixtures/fixtures/more_files/peekaview.m ================================================ // // peekaview.m // fixtures // // Created by Mark Larsen on 6/25/14. // Copyright (c) 2014 marklarr. All rights reserved. // #import "peekaview.h" @implementation peekaview - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { // Initialization code } return self; } /* // Only override drawRect: if you perform custom drawing. // An empty implementation adversely affects performance during animation. - (void)drawRect:(CGRect)rect { // Drawing code } */ @end ================================================ FILE: spec/fixtures/fixtures/other_fixtures.m ================================================ // // other_fixtures.m // fixtures // // Created by Mark Larsen on 6/24/14. // Copyright (c) 2014 marklarr. All rights reserved. // #import "other_fixtures.h" @implementation other_fixtures - (void)testedMethod { NSLog(@"tested"); } - (void)untestedMethod { NSLog(@"untested"); } @end ================================================ FILE: spec/fixtures/fixtures.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 155BBCA519E9CB6700BACB13 /* Branches.h in Headers */ = {isa = PBXBuildFile; fileRef = 155BBCA319E9CB6700BACB13 /* Branches.h */; }; 155BBCA619E9CB6700BACB13 /* Branches.m in Sources */ = {isa = PBXBuildFile; fileRef = 155BBCA419E9CB6700BACB13 /* Branches.m */; }; 155BBCA819E9CCC500BACB13 /* BranchesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 155BBCA719E9CCC500BACB13 /* BranchesTests.m */; }; 155D8AFE19FED984004666BA /* Empty.h in Headers */ = {isa = PBXBuildFile; fileRef = 155D8AFC19FED984004666BA /* Empty.h */; }; 155D8AFF19FED984004666BA /* Empty.m in Sources */ = {isa = PBXBuildFile; fileRef = 155D8AFD19FED984004666BA /* Empty.m */; }; 3773307C1CC42A58005EAF65 /* fixturesTwo.h in Headers */ = {isa = PBXBuildFile; fileRef = 3773307B1CC42A58005EAF65 /* fixturesTwo.h */; }; 3773307E1CC42A58005EAF65 /* fixturesTwo.m in Sources */ = {isa = PBXBuildFile; fileRef = 3773307D1CC42A58005EAF65 /* fixturesTwo.m */; }; 377330821CC42ABF005EAF65 /* libfixturesTwo.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 377330791CC42A58005EAF65 /* libfixturesTwo.dylib */; }; 8554482F1E461FAF0032518E /* fixturesTestsSecond.m in Sources */ = {isa = PBXBuildFile; fileRef = 8554482E1E461FAF0032518E /* fixturesTestsSecond.m */; }; 85AE69041E45F0F900DA2D47 /* libfixtures.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8C9A202D195965F10013B6B3 /* libfixtures.a */; }; 85AE69051E45F0F900DA2D47 /* libfixturesTwo.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 377330791CC42A58005EAF65 /* libfixturesTwo.dylib */; }; 85AE69061E45F0F900DA2D47 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8C9A2030195965F10013B6B3 /* Cocoa.framework */; }; 85AE69081E45F0F900DA2D47 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8C9A204A195965F10013B6B3 /* InfoPlist.strings */; }; 8C0F0B401ACF43A300793B7D /* fixtures_m.h in Headers */ = {isa = PBXBuildFile; fileRef = 8C0F0B3E1ACF43A300793B7D /* fixtures_m.h */; }; 8C0F0B411ACF43A300793B7D /* fixtures_m.m in Sources */ = {isa = PBXBuildFile; fileRef = 8C0F0B3F1ACF43A300793B7D /* fixtures_m.m */; }; 8C0F0B441ACF44E000793B7D /* fixtures_cpp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8C0F0B421ACF44E000793B7D /* fixtures_cpp.cpp */; }; 8C0F0B451ACF44E000793B7D /* fixtures_cpp.h in Headers */ = {isa = PBXBuildFile; fileRef = 8C0F0B431ACF44E000793B7D /* fixtures_cpp.h */; }; 8C0F0B481ACF44F200793B7D /* fixtures_mm.h in Headers */ = {isa = PBXBuildFile; fileRef = 8C0F0B461ACF44F200793B7D /* fixtures_mm.h */; }; 8C0F0B491ACF44F200793B7D /* fixtures_mm.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8C0F0B471ACF44F200793B7D /* fixtures_mm.mm */; }; 8C52AEF7195AAE32008A882A /* peekaview.h in Headers */ = {isa = PBXBuildFile; fileRef = 8C52AEF5195AAE32008A882A /* peekaview.h */; }; 8C52AEF8195AAE33008A882A /* peekaview.m in Sources */ = {isa = PBXBuildFile; fileRef = 8C52AEF6195AAE32008A882A /* peekaview.m */; }; 8C52AEFA195AAE70008A882A /* peekaviewTests💣.m in Sources */ = {isa = PBXBuildFile; fileRef = 8C52AEF9195AAE70008A882A /* peekaviewTests💣.m */; }; 8C9A2031195965F10013B6B3 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8C9A2030195965F10013B6B3 /* Cocoa.framework */; }; 8C9A203B195965F10013B6B3 /* fixtures.m in Sources */ = {isa = PBXBuildFile; fileRef = 8C9A203A195965F10013B6B3 /* fixtures.m */; }; 8C9A2043195965F10013B6B3 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8C9A2030195965F10013B6B3 /* Cocoa.framework */; }; 8C9A2046195965F10013B6B3 /* libfixtures.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8C9A202D195965F10013B6B3 /* libfixtures.a */; }; 8C9A204C195965F10013B6B3 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8C9A204A195965F10013B6B3 /* InfoPlist.strings */; }; 8C9A204E195965F10013B6B3 /* fixturesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8C9A204D195965F10013B6B3 /* fixturesTests.m */; }; B1E6981225F96B680086A3E2 /* FixtureFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = B1E6980425F96B670086A3E2 /* FixtureFramework.h */; settings = {ATTRIBUTES = (Public, ); }; }; B1E6982025F96B930086A3E2 /* FlashExperiment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1E6981F25F96B930086A3E2 /* FlashExperiment.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 85AE68FE1E45F0F900DA2D47 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 8C9A2025195965F00013B6B3 /* Project object */; proxyType = 1; remoteGlobalIDString = 8C9A202C195965F10013B6B3; remoteInfo = fixtures; }; 8C9A2044195965F10013B6B3 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 8C9A2025195965F00013B6B3 /* Project object */; proxyType = 1; remoteGlobalIDString = 8C9A202C195965F10013B6B3; remoteInfo = fixtures; }; B1E6982F25F96F490086A3E2 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 8C9A2025195965F00013B6B3 /* Project object */; proxyType = 1; remoteGlobalIDString = B1E6980125F96B670086A3E2; remoteInfo = FixtureFramework; }; B1E6983725F970320086A3E2 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 8C9A2025195965F00013B6B3 /* Project object */; proxyType = 1; remoteGlobalIDString = B1E6980125F96B670086A3E2; remoteInfo = FixtureFramework; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 155BBCA319E9CB6700BACB13 /* Branches.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Branches.h; sourceTree = ""; }; 155BBCA419E9CB6700BACB13 /* Branches.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Branches.m; sourceTree = ""; }; 155BBCA719E9CCC500BACB13 /* BranchesTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BranchesTests.m; sourceTree = ""; }; 155D8AFC19FED984004666BA /* Empty.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Empty.h; sourceTree = ""; }; 155D8AFD19FED984004666BA /* Empty.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Empty.m; sourceTree = ""; }; 377330791CC42A58005EAF65 /* libfixturesTwo.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libfixturesTwo.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; 3773307B1CC42A58005EAF65 /* fixturesTwo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = fixturesTwo.h; sourceTree = ""; }; 3773307D1CC42A58005EAF65 /* fixturesTwo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = fixturesTwo.m; sourceTree = ""; }; 8554482E1E461FAF0032518E /* fixturesTestsSecond.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = fixturesTestsSecond.m; sourceTree = ""; }; 85AE690C1E45F0F900DA2D47 /* fixturesTestsSecond.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = fixturesTestsSecond.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 8C0F0B3E1ACF43A300793B7D /* fixtures_m.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fixtures_m.h; sourceTree = ""; }; 8C0F0B3F1ACF43A300793B7D /* fixtures_m.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = fixtures_m.m; sourceTree = ""; }; 8C0F0B421ACF44E000793B7D /* fixtures_cpp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = fixtures_cpp.cpp; sourceTree = ""; }; 8C0F0B431ACF44E000793B7D /* fixtures_cpp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fixtures_cpp.h; sourceTree = ""; }; 8C0F0B461ACF44F200793B7D /* fixtures_mm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fixtures_mm.h; sourceTree = ""; }; 8C0F0B471ACF44F200793B7D /* fixtures_mm.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = fixtures_mm.mm; sourceTree = ""; }; 8C52AEF5195AAE32008A882A /* peekaview.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = peekaview.h; sourceTree = ""; }; 8C52AEF6195AAE32008A882A /* peekaview.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = peekaview.m; sourceTree = ""; }; 8C52AEF9195AAE70008A882A /* peekaviewTests💣.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "peekaviewTests💣.m"; sourceTree = ""; }; 8C9A202D195965F10013B6B3 /* libfixtures.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libfixtures.a; sourceTree = BUILT_PRODUCTS_DIR; }; 8C9A2030195965F10013B6B3 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 8C9A2033195965F10013B6B3 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 8C9A2034195965F10013B6B3 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 8C9A2035195965F10013B6B3 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 8C9A2038195965F10013B6B3 /* fixtures-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "fixtures-Prefix.pch"; sourceTree = ""; }; 8C9A2039195965F10013B6B3 /* fixtures.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = fixtures.h; sourceTree = ""; }; 8C9A203A195965F10013B6B3 /* fixtures.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = fixtures.m; sourceTree = ""; }; 8C9A2040195965F10013B6B3 /* fixturesTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = fixturesTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 8C9A2049195965F10013B6B3 /* fixturesTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "fixturesTests-Info.plist"; sourceTree = ""; }; 8C9A204B195965F10013B6B3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 8C9A204D195965F10013B6B3 /* fixturesTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = fixturesTests.m; sourceTree = ""; }; B1E6980225F96B670086A3E2 /* FixtureFramework.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FixtureFramework.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B1E6980425F96B670086A3E2 /* FixtureFramework.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FixtureFramework.h; sourceTree = ""; }; B1E6980525F96B670086A3E2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B1E6980F25F96B680086A3E2 /* FixtureFrameworkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FixtureFrameworkTests.swift; sourceTree = ""; }; B1E6981125F96B680086A3E2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B1E6981F25F96B930086A3E2 /* FlashExperiment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlashExperiment.swift; sourceTree = ""; }; B1E6982725F96BD30086A3E2 /* FlashExperimentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlashExperimentTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 377330761CC42A58005EAF65 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 85AE69031E45F0F900DA2D47 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 85AE69041E45F0F900DA2D47 /* libfixtures.a in Frameworks */, 85AE69051E45F0F900DA2D47 /* libfixturesTwo.dylib in Frameworks */, 85AE69061E45F0F900DA2D47 /* Cocoa.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 8C9A202A195965F10013B6B3 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 8C9A2031195965F10013B6B3 /* Cocoa.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 8C9A203D195965F10013B6B3 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 8C9A2046195965F10013B6B3 /* libfixtures.a in Frameworks */, 377330821CC42ABF005EAF65 /* libfixturesTwo.dylib in Frameworks */, 8C9A2043195965F10013B6B3 /* Cocoa.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; B1E697FF25F96B670086A3E2 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 3773307A1CC42A58005EAF65 /* fixturesTwo */ = { isa = PBXGroup; children = ( 3773307B1CC42A58005EAF65 /* fixturesTwo.h */, 3773307D1CC42A58005EAF65 /* fixturesTwo.m */, ); path = fixturesTwo; sourceTree = ""; }; 8C52AEF4195AAE16008A882A /* more_files */ = { isa = PBXGroup; children = ( 8C52AEF5195AAE32008A882A /* peekaview.h */, 8C52AEF6195AAE32008A882A /* peekaview.m */, 155BBCA319E9CB6700BACB13 /* Branches.h */, 155BBCA419E9CB6700BACB13 /* Branches.m */, 155D8AFC19FED984004666BA /* Empty.h */, 155D8AFD19FED984004666BA /* Empty.m */, ); path = more_files; sourceTree = ""; }; 8C9A2024195965F00013B6B3 = { isa = PBXGroup; children = ( 8C9A2036195965F10013B6B3 /* fixtures */, 3773307A1CC42A58005EAF65 /* fixturesTwo */, 8C9A2047195965F10013B6B3 /* fixturesTests */, B1E6980325F96B670086A3E2 /* FixtureFramework */, B1E6980E25F96B670086A3E2 /* FixtureFrameworkTests */, 8C9A202F195965F10013B6B3 /* Frameworks */, 8C9A202E195965F10013B6B3 /* Products */, ); sourceTree = ""; }; 8C9A202E195965F10013B6B3 /* Products */ = { isa = PBXGroup; children = ( 8C9A202D195965F10013B6B3 /* libfixtures.a */, 8C9A2040195965F10013B6B3 /* fixturesTests.xctest */, 377330791CC42A58005EAF65 /* libfixturesTwo.dylib */, 85AE690C1E45F0F900DA2D47 /* fixturesTestsSecond.xctest */, B1E6980225F96B670086A3E2 /* FixtureFramework.framework */, ); name = Products; sourceTree = ""; }; 8C9A202F195965F10013B6B3 /* Frameworks */ = { isa = PBXGroup; children = ( 8C9A2030195965F10013B6B3 /* Cocoa.framework */, 8C9A2032195965F10013B6B3 /* Other Frameworks */, ); name = Frameworks; sourceTree = ""; }; 8C9A2032195965F10013B6B3 /* Other Frameworks */ = { isa = PBXGroup; children = ( 8C9A2033195965F10013B6B3 /* Foundation.framework */, 8C9A2034195965F10013B6B3 /* CoreData.framework */, 8C9A2035195965F10013B6B3 /* AppKit.framework */, ); name = "Other Frameworks"; sourceTree = ""; }; 8C9A2036195965F10013B6B3 /* fixtures */ = { isa = PBXGroup; children = ( 8C52AEF4195AAE16008A882A /* more_files */, 8C9A2039195965F10013B6B3 /* fixtures.h */, 8C9A203A195965F10013B6B3 /* fixtures.m */, 8C0F0B3E1ACF43A300793B7D /* fixtures_m.h */, 8C0F0B3F1ACF43A300793B7D /* fixtures_m.m */, 8C9A2037195965F10013B6B3 /* Supporting Files */, 8C0F0B421ACF44E000793B7D /* fixtures_cpp.cpp */, 8C0F0B431ACF44E000793B7D /* fixtures_cpp.h */, 8C0F0B461ACF44F200793B7D /* fixtures_mm.h */, 8C0F0B471ACF44F200793B7D /* fixtures_mm.mm */, ); path = fixtures; sourceTree = ""; }; 8C9A2037195965F10013B6B3 /* Supporting Files */ = { isa = PBXGroup; children = ( 8C9A2038195965F10013B6B3 /* fixtures-Prefix.pch */, ); path = "Supporting Files"; sourceTree = ""; }; 8C9A2047195965F10013B6B3 /* fixturesTests */ = { isa = PBXGroup; children = ( 8C9A204D195965F10013B6B3 /* fixturesTests.m */, 8554482E1E461FAF0032518E /* fixturesTestsSecond.m */, 8C9A2048195965F10013B6B3 /* Supporting Files */, 8C52AEF9195AAE70008A882A /* peekaviewTests💣.m */, 155BBCA719E9CCC500BACB13 /* BranchesTests.m */, ); path = fixturesTests; sourceTree = ""; }; 8C9A2048195965F10013B6B3 /* Supporting Files */ = { isa = PBXGroup; children = ( 8C9A2049195965F10013B6B3 /* fixturesTests-Info.plist */, 8C9A204A195965F10013B6B3 /* InfoPlist.strings */, ); path = "Supporting Files"; sourceTree = ""; }; B1E6980325F96B670086A3E2 /* FixtureFramework */ = { isa = PBXGroup; children = ( B1E6980425F96B670086A3E2 /* FixtureFramework.h */, B1E6980525F96B670086A3E2 /* Info.plist */, B1E6981F25F96B930086A3E2 /* FlashExperiment.swift */, ); path = FixtureFramework; sourceTree = ""; }; B1E6980E25F96B670086A3E2 /* FixtureFrameworkTests */ = { isa = PBXGroup; children = ( B1E6980F25F96B680086A3E2 /* FixtureFrameworkTests.swift */, B1E6981125F96B680086A3E2 /* Info.plist */, B1E6982725F96BD30086A3E2 /* FlashExperimentTests.swift */, ); path = FixtureFrameworkTests; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 377330771CC42A58005EAF65 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 3773307C1CC42A58005EAF65 /* fixturesTwo.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; 8C9A202B195965F10013B6B3 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 8C0F0B481ACF44F200793B7D /* fixtures_mm.h in Headers */, 8C0F0B401ACF43A300793B7D /* fixtures_m.h in Headers */, 155D8AFE19FED984004666BA /* Empty.h in Headers */, 155BBCA519E9CB6700BACB13 /* Branches.h in Headers */, 8C0F0B451ACF44E000793B7D /* fixtures_cpp.h in Headers */, 8C52AEF7195AAE32008A882A /* peekaview.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; B1E697FD25F96B670086A3E2 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( B1E6981225F96B680086A3E2 /* FixtureFramework.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 377330781CC42A58005EAF65 /* fixturesTwo */ = { isa = PBXNativeTarget; buildConfigurationList = 3773307F1CC42A58005EAF65 /* Build configuration list for PBXNativeTarget "fixturesTwo" */; buildPhases = ( 377330751CC42A58005EAF65 /* Sources */, 377330761CC42A58005EAF65 /* Frameworks */, 377330771CC42A58005EAF65 /* Headers */, ); buildRules = ( ); dependencies = ( ); name = fixturesTwo; productName = fixturesTwo; productReference = 377330791CC42A58005EAF65 /* libfixturesTwo.dylib */; productType = "com.apple.product-type.library.dynamic"; }; 85AE68FC1E45F0F900DA2D47 /* fixturesTestsSecond */ = { isa = PBXNativeTarget; buildConfigurationList = 85AE69091E45F0F900DA2D47 /* Build configuration list for PBXNativeTarget "fixturesTestsSecond" */; buildPhases = ( 85AE68FF1E45F0F900DA2D47 /* Sources */, 85AE69031E45F0F900DA2D47 /* Frameworks */, 85AE69071E45F0F900DA2D47 /* Resources */, ); buildRules = ( ); dependencies = ( 85AE68FD1E45F0F900DA2D47 /* PBXTargetDependency */, ); name = fixturesTestsSecond; productName = fixturesTests; productReference = 85AE690C1E45F0F900DA2D47 /* fixturesTestsSecond.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 8C9A202C195965F10013B6B3 /* fixtures */ = { isa = PBXNativeTarget; buildConfigurationList = 8C9A2051195965F10013B6B3 /* Build configuration list for PBXNativeTarget "fixtures" */; buildPhases = ( 8C9A2029195965F10013B6B3 /* Sources */, 8C9A202A195965F10013B6B3 /* Frameworks */, 8C9A202B195965F10013B6B3 /* Headers */, ); buildRules = ( ); dependencies = ( B1E6983025F96F490086A3E2 /* PBXTargetDependency */, ); name = fixtures; productName = fixtures; productReference = 8C9A202D195965F10013B6B3 /* libfixtures.a */; productType = "com.apple.product-type.library.static"; }; 8C9A203F195965F10013B6B3 /* fixturesTests */ = { isa = PBXNativeTarget; buildConfigurationList = 8C9A2054195965F10013B6B3 /* Build configuration list for PBXNativeTarget "fixturesTests" */; buildPhases = ( 8C9A203C195965F10013B6B3 /* Sources */, 8C9A203D195965F10013B6B3 /* Frameworks */, 8C9A203E195965F10013B6B3 /* Resources */, ); buildRules = ( ); dependencies = ( B1E6983825F970320086A3E2 /* PBXTargetDependency */, 8C9A2045195965F10013B6B3 /* PBXTargetDependency */, ); name = fixturesTests; productName = fixturesTests; productReference = 8C9A2040195965F10013B6B3 /* fixturesTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; B1E6980125F96B670086A3E2 /* FixtureFramework */ = { isa = PBXNativeTarget; buildConfigurationList = B1E6981725F96B680086A3E2 /* Build configuration list for PBXNativeTarget "FixtureFramework" */; buildPhases = ( B1E697FD25F96B670086A3E2 /* Headers */, B1E697FE25F96B670086A3E2 /* Sources */, B1E697FF25F96B670086A3E2 /* Frameworks */, B1E6980025F96B670086A3E2 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = FixtureFramework; productName = FixtureFramework; productReference = B1E6980225F96B670086A3E2 /* FixtureFramework.framework */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 8C9A2025195965F00013B6B3 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1240; LastUpgradeCheck = 0510; ORGANIZATIONNAME = marklarr; TargetAttributes = { 377330781CC42A58005EAF65 = { CreatedOnToolsVersion = 7.3; }; B1E6980125F96B670086A3E2 = { CreatedOnToolsVersion = 12.4; LastSwiftMigration = 1240; ProvisioningStyle = Automatic; }; }; }; buildConfigurationList = 8C9A2028195965F00013B6B3 /* Build configuration list for PBXProject "fixtures" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( English, en, ); mainGroup = 8C9A2024195965F00013B6B3; productRefGroup = 8C9A202E195965F10013B6B3 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 8C9A202C195965F10013B6B3 /* fixtures */, 377330781CC42A58005EAF65 /* fixturesTwo */, 8C9A203F195965F10013B6B3 /* fixturesTests */, 85AE68FC1E45F0F900DA2D47 /* fixturesTestsSecond */, B1E6980125F96B670086A3E2 /* FixtureFramework */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 85AE69071E45F0F900DA2D47 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 85AE69081E45F0F900DA2D47 /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 8C9A203E195965F10013B6B3 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 8C9A204C195965F10013B6B3 /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; B1E6980025F96B670086A3E2 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 377330751CC42A58005EAF65 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 3773307E1CC42A58005EAF65 /* fixturesTwo.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 85AE68FF1E45F0F900DA2D47 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 8554482F1E461FAF0032518E /* fixturesTestsSecond.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 8C9A2029195965F10013B6B3 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 8C9A203B195965F10013B6B3 /* fixtures.m in Sources */, 155D8AFF19FED984004666BA /* Empty.m in Sources */, 8C0F0B411ACF43A300793B7D /* fixtures_m.m in Sources */, 8C0F0B441ACF44E000793B7D /* fixtures_cpp.cpp in Sources */, 155BBCA619E9CB6700BACB13 /* Branches.m in Sources */, 8C0F0B491ACF44F200793B7D /* fixtures_mm.mm in Sources */, 8C52AEF8195AAE33008A882A /* peekaview.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 8C9A203C195965F10013B6B3 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 8C9A204E195965F10013B6B3 /* fixturesTests.m in Sources */, 8C52AEFA195AAE70008A882A /* peekaviewTests💣.m in Sources */, 155BBCA819E9CCC500BACB13 /* BranchesTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; B1E697FE25F96B670086A3E2 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( B1E6982025F96B930086A3E2 /* FlashExperiment.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 85AE68FD1E45F0F900DA2D47 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 8C9A202C195965F10013B6B3 /* fixtures */; targetProxy = 85AE68FE1E45F0F900DA2D47 /* PBXContainerItemProxy */; }; 8C9A2045195965F10013B6B3 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 8C9A202C195965F10013B6B3 /* fixtures */; targetProxy = 8C9A2044195965F10013B6B3 /* PBXContainerItemProxy */; }; B1E6983025F96F490086A3E2 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = B1E6980125F96B670086A3E2 /* FixtureFramework */; targetProxy = B1E6982F25F96F490086A3E2 /* PBXContainerItemProxy */; }; B1E6983825F970320086A3E2 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = B1E6980125F96B670086A3E2 /* FixtureFramework */; targetProxy = B1E6983725F970320086A3E2 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 8C9A204A195965F10013B6B3 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( 8C9A204B195965F10013B6B3 /* en */, ); name = InfoPlist.strings; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 377330801CC42A58005EAF65 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_WARN_UNREACHABLE_CODE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; EXECUTABLE_PREFIX = lib; GCC_NO_COMMON_BLOCKS = YES; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 377330811CC42A58005EAF65 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_WARN_UNREACHABLE_CODE = YES; COPY_PHASE_STRIP = NO; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; ENABLE_STRICT_OBJC_MSGSEND = YES; EXECUTABLE_PREFIX = lib; GCC_NO_COMMON_BLOCKS = YES; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; 85AE690A1E45F0F900DA2D47 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(DEVELOPER_FRAMEWORKS_DIR)", "$(inherited)", ); GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "fixtures/Supporting Files/fixtures-Prefix.pch"; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = xctest; }; name = Debug; }; 85AE690B1E45F0F900DA2D47 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(DEVELOPER_FRAMEWORKS_DIR)", "$(inherited)", ); GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "fixtures/Supporting Files/fixtures-Prefix.pch"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = xctest; }; name = Release; }; 8C9A204F195965F10013B6B3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_GENERATE_TEST_COVERAGE_FILES = YES; GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.9; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; }; name = Debug; }; 8C9A2050195965F10013B6B3 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_GENERATE_TEST_COVERAGE_FILES = YES; GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.9; SDKROOT = macosx; }; name = Release; }; 8C9A2052195965F10013B6B3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { GCC_GENERATE_TEST_COVERAGE_FILES = YES; GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "fixtures/Supporting Files/fixtures-Prefix.pch"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 8C9A2053195965F10013B6B3 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { GCC_GENERATE_TEST_COVERAGE_FILES = YES; GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "fixtures/Supporting Files/fixtures-Prefix.pch"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; 8C9A2055195965F10013B6B3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(DEVELOPER_FRAMEWORKS_DIR)", "$(inherited)", ); GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "fixtures/Supporting Files/fixtures-Prefix.pch"; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); INFOPLIST_FILE = "fixturesTests/Supporting Files/fixturesTests-Info.plist"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = xctest; }; name = Debug; }; 8C9A2056195965F10013B6B3 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(DEVELOPER_FRAMEWORKS_DIR)", "$(inherited)", ); GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "fixtures/Supporting Files/fixtures-Prefix.pch"; INFOPLIST_FILE = "fixturesTests/Supporting Files/fixturesTests-Info.plist"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = xctest; }; name = Release; }; B1E6981325F96B680086A3E2 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = FixtureFramework/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 11.1; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.onato.FixtureFramework; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; B1E6981425F96B680086A3E2 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = FixtureFramework/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 11.1; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.onato.FixtureFramework; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 3773307F1CC42A58005EAF65 /* Build configuration list for PBXNativeTarget "fixturesTwo" */ = { isa = XCConfigurationList; buildConfigurations = ( 377330801CC42A58005EAF65 /* Debug */, 377330811CC42A58005EAF65 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 85AE69091E45F0F900DA2D47 /* Build configuration list for PBXNativeTarget "fixturesTestsSecond" */ = { isa = XCConfigurationList; buildConfigurations = ( 85AE690A1E45F0F900DA2D47 /* Debug */, 85AE690B1E45F0F900DA2D47 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 8C9A2028195965F00013B6B3 /* Build configuration list for PBXProject "fixtures" */ = { isa = XCConfigurationList; buildConfigurations = ( 8C9A204F195965F10013B6B3 /* Debug */, 8C9A2050195965F10013B6B3 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 8C9A2051195965F10013B6B3 /* Build configuration list for PBXNativeTarget "fixtures" */ = { isa = XCConfigurationList; buildConfigurations = ( 8C9A2052195965F10013B6B3 /* Debug */, 8C9A2053195965F10013B6B3 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 8C9A2054195965F10013B6B3 /* Build configuration list for PBXNativeTarget "fixturesTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 8C9A2055195965F10013B6B3 /* Debug */, 8C9A2056195965F10013B6B3 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; B1E6981725F96B680086A3E2 /* Build configuration list for PBXNativeTarget "FixtureFramework" */ = { isa = XCConfigurationList; buildConfigurations = ( B1E6981325F96B680086A3E2 /* Debug */, B1E6981425F96B680086A3E2 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 8C9A2025195965F00013B6B3 /* Project object */; } ================================================ FILE: spec/fixtures/fixtures.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: spec/fixtures/fixtures.xcodeproj/xcshareddata/xcschemes/aggregateFixturesTests.xcscheme ================================================ ================================================ FILE: spec/fixtures/fixtures.xcodeproj/xcshareddata/xcschemes/fixtures.xcscheme ================================================ ================================================ FILE: spec/fixtures/fixtures.xcodeproj/xcshareddata/xcschemes/fixturesTests.xcscheme ================================================ ================================================ FILE: spec/fixtures/fixtures.xcodeproj/xcshareddata/xcschemes/fixturesTestsSecond.xcscheme ================================================ ================================================ FILE: spec/fixtures/fixtures.xcodeproj/xcshareddata/xcschemes/fixturesTwo.xcscheme ================================================ ================================================ FILE: spec/fixtures/fixtures.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: spec/fixtures/fixtures.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: spec/fixtures/fixtures.xcworkspace/xcshareddata/xcschemes/fixturesTestsWorkspace.xcscheme ================================================ ================================================ FILE: spec/fixtures/fixturesTests/BranchesTests.m ================================================ // // BranchesTests.m // fixtures // // Created by Julian Krumow on 11.10.14. // Copyright (c) 2014 marklarr. All rights reserved. // #import #import "Branches.h" @interface BranchesTests : XCTestCase @end @implementation BranchesTests - (void)setUp { [super setUp]; // Put setup code here. This method is called before the invocation of each test method in the class. } - (void)tearDown { // Put teardown code here. This method is called after the invocation of each test method in the class. [super tearDown]; } - (void)testBranchesNoBranches { Branches *branches = [[Branches alloc] init]; [branches branches:NO skipBranches:NO]; } - (void)testBranchesFirstBranchAndSkip { Branches *branches = [[Branches alloc] init]; [branches branches:YES skipBranches:YES]; } @end ================================================ FILE: spec/fixtures/fixturesTests/Supporting Files/en.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ ================================================ FILE: spec/fixtures/fixturesTests/Supporting Files/fixturesTests-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier marklarr.${PRODUCT_NAME:rfc1034identifier} CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 ================================================ FILE: spec/fixtures/fixturesTests/fixturesTests.m ================================================ // // fixturesTests.m // fixturesTests // // Created by Mark Larsen on 6/24/14. // Copyright (c) 2014 marklarr. All rights reserved. // #import #import "fixtures.h" #import "fixturesTwo.h" @interface fixturesTests : XCTestCase @end @implementation fixturesTests - (void)setUp { [super setUp]; // Put setup code here. This method is called before the invocation of each test method in the class. } - (void)tearDown { // Put teardown code here. This method is called after the invocation of each test method in the class. [super tearDown]; } - (void)testExample { fixtures *f = [[fixtures alloc] init]; [f testedMethod]; } - (void)testFixturesTwo { fixturesTwo *f2 = [[fixturesTwo alloc] init]; XCTAssertEqual([f2 doSomething], 11); } @end ================================================ FILE: spec/fixtures/fixturesTests/fixturesTestsSecond.m ================================================ // // fixturesTestsSecond.m // fixturesTests // // Created by Mark Larsen on 6/24/14. // Copyright (c) 2014 marklarr. All rights reserved. // #import @interface fixturesTestsSecond : XCTestCase @end @implementation fixturesTestsSecond - (void)testExample { } @end ================================================ FILE: spec/fixtures/fixturesTests/peekaviewTests💣.m ================================================ // // peekaviewTests💣.m // fixtures // // Created by Mark Larsen on 6/25/14. // Copyright (c) 2014 marklarr. All rights reserved. // #import @interface peekaviewTests : XCTestCase @end @implementation peekaviewTests - (void)setUp { [super setUp]; // Put setup code here. This method is called before the invocation of each test method in the class. } - (void)tearDown { // Put teardown code here. This method is called after the invocation of each test method in the class. [super tearDown]; } - (void)testExample { XCTAssert(YES, @"woot"); } @end ================================================ FILE: spec/fixtures/fixturesTwo/fixturesTwo.h ================================================ // // fixturesTwo.h // fixturesTwo // // Created by Kent Sutherland on 4/17/16. // Copyright © 2016 marklarr. All rights reserved. // #import @interface fixturesTwo : NSObject - (NSInteger)doSomething; @end ================================================ FILE: spec/fixtures/fixturesTwo/fixturesTwo.m ================================================ // // fixturesTwo.m // fixturesTwo // // Created by Kent Sutherland on 4/17/16. // Copyright © 2016 marklarr. All rights reserved. // #import "fixturesTwo.h" @implementation fixturesTwo - (NSInteger)doSomething { NSInteger a = 5; NSInteger b = 6; return a + b; } @end ================================================ FILE: spec/fixtures/fixtures_html/Branches.m.html ================================================ Branches.m - Slather
Slather logo

Coverage for "Branches.m" : 43.33%

(13 of 30 relevant lines covered)

spec/fixtures/fixtures/more_files/Branches.m

1
//
2
//  Branches.m
3
//  fixtures
4
//
5
//  Created by Julian Krumow on 11.10.14.
6
//  Copyright (c) 2014 marklarr. All rights reserved.
7
//
8
9
#import "Branches.h"
10
11
@implementation Branches
12
13
- (void)branches:(BOOL)goIf skipBranches:(BOOL)skipBranches
14
{
2x
15
    if (goIf) {
2x
16
        NSLog(@"foo.");
1x
17
        
1x
18
        if (!skipBranches) {
1x
19
            NSLog(@"not skipped.");
!
20
        }
!
21
    } else {
1x
22
        NSLog(@"bar.");
1x
23
    }
1x
24
    
2x
25
    int i = 5;
2x
26
    if (i == 5) {
2x
27
        return;
2x
28
    }
2x
29
    switch (i) {
!
30
        case 0:
!
31
            NSLog(@"0");
!
32
            break;
!
33
            
!
34
        case 1:
!
35
            NSLog(@"1");
!
36
            break;
!
37
        case 5:
!
38
            NSLog(@"5");
!
39
            break;
!
40
        default:
!
41
            break;
!
42
    }
!
43
}
!
44
45
@end
================================================ FILE: spec/fixtures/fixtures_html/BranchesTests.m.html ================================================ BranchesTests.m - Slather
Slather logo

Coverage for "BranchesTests.m" : 100.00%

(16 of 16 relevant lines covered)

spec/fixtures/fixturesTests/BranchesTests.m

1
//
2
//  BranchesTests.m
3
//  fixtures
4
//
5
//  Created by Julian Krumow on 11.10.14.
6
//  Copyright (c) 2014 marklarr. All rights reserved.
7
//
8
9
#import <XCTest/XCTest.h>
10
#import "Branches.h"
11
12
@interface BranchesTests : XCTestCase
13
14
@end
15
16
@implementation BranchesTests
17
18
- (void)setUp {
2x
19
    [super setUp];
2x
20
    // Put setup code here. This method is called before the invocation of each test method in the class.
2x
21
}
2x
22
23
- (void)tearDown {
2x
24
    // Put teardown code here. This method is called after the invocation of each test method in the class.
2x
25
    [super tearDown];
2x
26
}
2x
27
28
- (void)testBranchesNoBranches {
1x
29
    Branches *branches = [[Branches alloc] init];
1x
30
    [branches branches:NO skipBranches:NO];
1x
31
}
1x
32
33
- (void)testBranchesFirstBranchAndSkip {
1x
34
    Branches *branches = [[Branches alloc] init];
1x
35
    [branches branches:YES skipBranches:YES];
1x
36
}
1x
37
38
@end
================================================ FILE: spec/fixtures/fixtures_html/fixtures.m.html ================================================ fixtures.m - Slather
Slather logo

Coverage for "fixtures.m" : 50.00%

(3 of 6 relevant lines covered)

spec/fixtures/fixtures/fixtures.m

1
//
2
//  fixtures.m
3
//  fixtures
4
//
5
//  Created by Mark Larsen on 6/24/14.
6
//  Copyright (c) 2014 marklarr 🌟. All rights reserved.
7
//
8
9
#import "fixtures.h"
10
11
@implementation fixtures
12
13
- (void)testedMethod
14
{
1x
15
    NSLog(@"tested");
1x
16
}
1x
17
18
- (void)untestedMethod
19
{
!
20
    NSLog(@"untested");
!
21
}
!
22
23
@end
================================================ FILE: spec/fixtures/fixtures_html/fixturesTests.m.html ================================================ fixturesTests.m - Slather
Slather logo

Coverage for "fixturesTests.m" : 100.00%

(17 of 17 relevant lines covered)

spec/fixtures/fixturesTests/fixturesTests.m

1
//
2
//  fixturesTests.m
3
//  fixturesTests
4
//
5
//  Created by Mark Larsen on 6/24/14.
6
//  Copyright (c) 2014 marklarr. All rights reserved.
7
//
8
9
#import <XCTest/XCTest.h>
10
#import "fixtures.h"
11
#import "fixturesTwo.h"
12
13
@interface fixturesTests : XCTestCase
14
15
@end
16
17
@implementation fixturesTests
18
19
- (void)setUp
20
{
2x
21
    [super setUp];
2x
22
    // Put setup code here. This method is called before the invocation of each test method in the class.
2x
23
}
2x
24
25
- (void)tearDown
26
{
2x
27
    // Put teardown code here. This method is called after the invocation of each test method in the class.
2x
28
    [super tearDown];
2x
29
}
2x
30
31
- (void)testExample
32
{
1x
33
    fixtures *f = [[fixtures alloc] init];
1x
34
    [f testedMethod];
1x
35
}
1x
36
37
- (void)testFixturesTwo
38
{
1x
39
    fixturesTwo *f2 = [[fixturesTwo alloc] init];
1x
40
1x
41
    XCTAssertEqual([f2 doSomething], 11);
1x
42
}
1x
43
44
@end
================================================ FILE: spec/fixtures/fixtures_html/index.html ================================================ fixtures.xcodeproj - Slather
Slather logo

Files for "fixtures.xcodeproj"

Total Coverage : 75.00%

% File Lines Relevant Covered Missed
50.00 fixtures.m 23 6 3 3
43.33 Branches.m 45 30 13 17
100.00 BranchesTests.m 38 16 16 0
100.00 fixturesTests.m 44 17 17 0
100.00 peekaviewTests💣.m 34 11 11 0
================================================ FILE: spec/fixtures/fixtures_html/peekaviewTests💣.m.html ================================================ peekaviewTests💣.m - Slather
Slather logo

Coverage for "peekaviewTests💣.m" : 100.00%

(11 of 11 relevant lines covered)

spec/fixtures/fixturesTests/peekaviewTests💣.m

1
//
2
//  peekaviewTests💣.m
3
//  fixtures
4
//
5
//  Created by Mark Larsen on 6/25/14.
6
//  Copyright (c) 2014 marklarr. All rights reserved.
7
//
8
9
#import <XCTest/XCTest.h>
10
11
@interface peekaviewTests : XCTestCase
12
13
@end
14
15
@implementation peekaviewTests
16
17
- (void)setUp
18
{
1x
19
    [super setUp];
1x
20
    // Put setup code here. This method is called before the invocation of each test method in the class.
1x
21
}
1x
22
23
- (void)tearDown
24
{
1x
25
    // Put teardown code here. This method is called after the invocation of each test method in the class.
1x
26
    [super tearDown];
1x
27
}
1x
28
29
- (void)testExample
30
{
1x
31
    XCTAssert(YES, @"woot");
1x
32
}
1x
33
34
@end
================================================ FILE: spec/fixtures/gutter.json ================================================ {"meta":{"timestamp":"2016-04-17 16:53:43.919308"},"symbols_by_file":{"spec/fixtures/fixtures/fixtures.m":[{"line":1,"long_text":"","short_text":"-"},{"line":2,"long_text":"","short_text":"-"},{"line":3,"long_text":"","short_text":"-"},{"line":4,"long_text":"","short_text":"-"},{"line":5,"long_text":"","short_text":"-"},{"line":6,"long_text":"","short_text":"-"},{"line":7,"long_text":"","short_text":"-"},{"line":8,"long_text":"","short_text":"-"},{"line":9,"long_text":"","short_text":"-"},{"line":10,"long_text":"","short_text":"-"},{"line":11,"long_text":"","short_text":"-"},{"line":12,"long_text":"","short_text":"-"},{"line":13,"long_text":"","short_text":"-"},{"line":14,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":15,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":16,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":17,"long_text":"","short_text":"-"},{"line":18,"long_text":"","short_text":"-"},{"line":19,"long_text":"","short_text":"0","background_color":"0xFC635E"},{"line":20,"long_text":"","short_text":"0","background_color":"0xFC635E"},{"line":21,"long_text":"","short_text":"0","background_color":"0xFC635E"},{"line":22,"long_text":"","short_text":"-"},{"line":23,"long_text":"","short_text":"-"}],"spec/fixtures/fixtures/more_files/Branches.m":[{"line":1,"long_text":"","short_text":"-"},{"line":2,"long_text":"","short_text":"-"},{"line":3,"long_text":"","short_text":"-"},{"line":4,"long_text":"","short_text":"-"},{"line":5,"long_text":"","short_text":"-"},{"line":6,"long_text":"","short_text":"-"},{"line":7,"long_text":"","short_text":"-"},{"line":8,"long_text":"","short_text":"-"},{"line":9,"long_text":"","short_text":"-"},{"line":10,"long_text":"","short_text":"-"},{"line":11,"long_text":"","short_text":"-"},{"line":12,"long_text":"","short_text":"-"},{"line":13,"long_text":"","short_text":"-"},{"line":14,"long_text":"","short_text":"2","background_color":"0x35CC4B"},{"line":15,"long_text":"","short_text":"2","background_color":"0x35CC4B"},{"line":16,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":17,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":18,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":19,"long_text":"","short_text":"0","background_color":"0xFC635E"},{"line":20,"long_text":"","short_text":"0","background_color":"0xFC635E"},{"line":21,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":22,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":23,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":24,"long_text":"","short_text":"2","background_color":"0x35CC4B"},{"line":25,"long_text":"","short_text":"2","background_color":"0x35CC4B"},{"line":26,"long_text":"","short_text":"2","background_color":"0x35CC4B"},{"line":27,"long_text":"","short_text":"2","background_color":"0x35CC4B"},{"line":28,"long_text":"","short_text":"2","background_color":"0x35CC4B"},{"line":29,"long_text":"","short_text":"0","background_color":"0xFC635E"},{"line":30,"long_text":"","short_text":"0","background_color":"0xFC635E"},{"line":31,"long_text":"","short_text":"0","background_color":"0xFC635E"},{"line":32,"long_text":"","short_text":"0","background_color":"0xFC635E"},{"line":33,"long_text":"","short_text":"0","background_color":"0xFC635E"},{"line":34,"long_text":"","short_text":"0","background_color":"0xFC635E"},{"line":35,"long_text":"","short_text":"0","background_color":"0xFC635E"},{"line":36,"long_text":"","short_text":"0","background_color":"0xFC635E"},{"line":37,"long_text":"","short_text":"0","background_color":"0xFC635E"},{"line":38,"long_text":"","short_text":"0","background_color":"0xFC635E"},{"line":39,"long_text":"","short_text":"0","background_color":"0xFC635E"},{"line":40,"long_text":"","short_text":"0","background_color":"0xFC635E"},{"line":41,"long_text":"","short_text":"0","background_color":"0xFC635E"},{"line":42,"long_text":"","short_text":"0","background_color":"0xFC635E"},{"line":43,"long_text":"","short_text":"0","background_color":"0xFC635E"},{"line":44,"long_text":"","short_text":"-"},{"line":45,"long_text":"","short_text":"-"}],"spec/fixtures/fixturesTests/BranchesTests.m":[{"line":1,"long_text":"","short_text":"-"},{"line":2,"long_text":"","short_text":"-"},{"line":3,"long_text":"","short_text":"-"},{"line":4,"long_text":"","short_text":"-"},{"line":5,"long_text":"","short_text":"-"},{"line":6,"long_text":"","short_text":"-"},{"line":7,"long_text":"","short_text":"-"},{"line":8,"long_text":"","short_text":"-"},{"line":9,"long_text":"","short_text":"-"},{"line":10,"long_text":"","short_text":"-"},{"line":11,"long_text":"","short_text":"-"},{"line":12,"long_text":"","short_text":"-"},{"line":13,"long_text":"","short_text":"-"},{"line":14,"long_text":"","short_text":"-"},{"line":15,"long_text":"","short_text":"-"},{"line":16,"long_text":"","short_text":"-"},{"line":17,"long_text":"","short_text":"-"},{"line":18,"long_text":"","short_text":"2","background_color":"0x35CC4B"},{"line":19,"long_text":"","short_text":"2","background_color":"0x35CC4B"},{"line":20,"long_text":"","short_text":"2","background_color":"0x35CC4B"},{"line":21,"long_text":"","short_text":"2","background_color":"0x35CC4B"},{"line":22,"long_text":"","short_text":"-"},{"line":23,"long_text":"","short_text":"2","background_color":"0x35CC4B"},{"line":24,"long_text":"","short_text":"2","background_color":"0x35CC4B"},{"line":25,"long_text":"","short_text":"2","background_color":"0x35CC4B"},{"line":26,"long_text":"","short_text":"2","background_color":"0x35CC4B"},{"line":27,"long_text":"","short_text":"-"},{"line":28,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":29,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":30,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":31,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":32,"long_text":"","short_text":"-"},{"line":33,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":34,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":35,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":36,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":37,"long_text":"","short_text":"-"},{"line":38,"long_text":"","short_text":"-"}],"spec/fixtures/fixturesTests/fixturesTests.m":[{"line":1,"long_text":"","short_text":"-"},{"line":2,"long_text":"","short_text":"-"},{"line":3,"long_text":"","short_text":"-"},{"line":4,"long_text":"","short_text":"-"},{"line":5,"long_text":"","short_text":"-"},{"line":6,"long_text":"","short_text":"-"},{"line":7,"long_text":"","short_text":"-"},{"line":8,"long_text":"","short_text":"-"},{"line":9,"long_text":"","short_text":"-"},{"line":10,"long_text":"","short_text":"-"},{"line":11,"long_text":"","short_text":"-"},{"line":12,"long_text":"","short_text":"-"},{"line":13,"long_text":"","short_text":"-"},{"line":14,"long_text":"","short_text":"-"},{"line":15,"long_text":"","short_text":"-"},{"line":16,"long_text":"","short_text":"-"},{"line":17,"long_text":"","short_text":"-"},{"line":18,"long_text":"","short_text":"-"},{"line":19,"long_text":"","short_text":"-"},{"line":20,"long_text":"","short_text":"2","background_color":"0x35CC4B"},{"line":21,"long_text":"","short_text":"2","background_color":"0x35CC4B"},{"line":22,"long_text":"","short_text":"2","background_color":"0x35CC4B"},{"line":23,"long_text":"","short_text":"2","background_color":"0x35CC4B"},{"line":24,"long_text":"","short_text":"-"},{"line":25,"long_text":"","short_text":"-"},{"line":26,"long_text":"","short_text":"2","background_color":"0x35CC4B"},{"line":27,"long_text":"","short_text":"2","background_color":"0x35CC4B"},{"line":28,"long_text":"","short_text":"2","background_color":"0x35CC4B"},{"line":29,"long_text":"","short_text":"2","background_color":"0x35CC4B"},{"line":30,"long_text":"","short_text":"-"},{"line":31,"long_text":"","short_text":"-"},{"line":32,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":33,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":34,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":35,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":36,"long_text":"","short_text":"-"},{"line":37,"long_text":"","short_text":"-"},{"line":38,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":39,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":40,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":41,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":42,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":43,"long_text":"","short_text":"-"},{"line":44,"long_text":"","short_text":"-"}],"spec/fixtures/fixturesTests/peekaviewTests💣.m":[{"line":1,"long_text":"","short_text":"-"},{"line":2,"long_text":"","short_text":"-"},{"line":3,"long_text":"","short_text":"-"},{"line":4,"long_text":"","short_text":"-"},{"line":5,"long_text":"","short_text":"-"},{"line":6,"long_text":"","short_text":"-"},{"line":7,"long_text":"","short_text":"-"},{"line":8,"long_text":"","short_text":"-"},{"line":9,"long_text":"","short_text":"-"},{"line":10,"long_text":"","short_text":"-"},{"line":11,"long_text":"","short_text":"-"},{"line":12,"long_text":"","short_text":"-"},{"line":13,"long_text":"","short_text":"-"},{"line":14,"long_text":"","short_text":"-"},{"line":15,"long_text":"","short_text":"-"},{"line":16,"long_text":"","short_text":"-"},{"line":17,"long_text":"","short_text":"-"},{"line":18,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":19,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":20,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":21,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":22,"long_text":"","short_text":"-"},{"line":23,"long_text":"","short_text":"-"},{"line":24,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":25,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":26,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":27,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":28,"long_text":"","short_text":"-"},{"line":29,"long_text":"","short_text":"-"},{"line":30,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":31,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":32,"long_text":"","short_text":"1","background_color":"0x35CC4B"},{"line":33,"long_text":"","short_text":"-"},{"line":34,"long_text":"","short_text":"-"}]}} ================================================ FILE: spec/fixtures/sonarqube-generic-coverage.xml ================================================ ================================================ FILE: spec/slather/cocoapods_plugin_spec.rb ================================================ require 'cocoapods' require File.join(File.dirname(__FILE__), '..', 'spec_helper') require File.join(File.dirname(__FILE__), '../../lib/cocoapods_plugin') describe Slather do describe 'CocoaPods Plugin' do it 'should setup slather for coverage in the Pods project' do mock_project = double(Xcodeproj::Project) allow(Xcodeproj::Project).to receive(:open).and_return(mock_project) expect(mock_project).to receive(:slather_setup_for_coverage) expect(mock_project).to receive(:save) # Execute the post_install hook via CocoaPods sandbox_root = 'Pods' sandbox = Pod::Sandbox.new(sandbox_root) context = Pod::Installer::PostInstallHooksContext.generate(sandbox, mock_project, []) Pod::HooksManager.run(:post_install, context, {'slather' => nil}) end end end ================================================ FILE: spec/slather/coverage_file_spec.rb ================================================ require File.join(File.dirname(__FILE__), '..', 'spec_helper') describe Slather::CoverageFile do let(:fixtures_project) do project = Slather::Project.open(FIXTURES_PROJECT_PATH) project.build_directory = TEMP_DERIVED_DATA_PATH project.send(:configure) allow(project).to receive(:input_format).and_return("gcov") project end let(:coverage_file) do fixtures_project.coverage_files.detect { |cf| cf.source_file_pathname.basename.to_s == "fixtures.m" } end describe "#initialize" do it "should convert the provided path string to a Pathname object, and set it as the gcno_file_pathname" do expect(coverage_file.gcno_file_pathname).to be_exist expect(coverage_file.gcno_file_pathname.basename.to_s).to eq("fixtures.gcno") end end describe "#source_file_pathname" do it "should look in the source_directory if it has been set on the project" do coverage_file = Slather::CoverageFile.new(fixtures_project, "fixtures.m") fixtures_project.source_directory = Pathname(File.join(File.dirname(__FILE__), '../fixtures/')).realpath.to_s expect(coverage_file.source_file_pathname).to eq(fixtures_project["fixtures/fixtures.m"].real_path) end it "should look in the source_directory if it has been set on the project" do coverage_file = Slather::CoverageFile.new(fixtures_project, "fixtures.m") fixtures_project.source_directory = Pathname(File.join(File.dirname(__FILE__), '../fixtures/fixturesTests')).realpath.to_s expect(coverage_file.source_file_pathname).to be_nil end it "should return nil if it couldn't find the coverage files's source implementation file in the project" do whoami_file = Slather::CoverageFile.new(fixtures_project, "/some/path/whoami.gcno") expect(whoami_file.source_file_pathname).to be_nil end ["cpp", "mm", "m"].each do |file_ext| it "should work for #{file_ext} files" do file_name = "fixtures_#{file_ext}.#{file_ext}" coverage_file = fixtures_project.coverage_files.detect { |cf| cf.source_file_pathname.basename.to_s == file_name } expect(coverage_file.source_file_pathname).to eq(fixtures_project["fixtures/#{file_name}"].real_path) end end end describe "#source_file" do it "should return a file object for the source_file_pathname" do file = coverage_file.source_file expect(file).to be_a File expect(Pathname(file.path)).to eq(coverage_file.source_file_pathname) end end describe "#source_data" do it "should return the contents of the source_file" do expected = <<-OBJC // // fixtures.m // fixtures // // Created by Mark Larsen on 6/24/14. // Copyright (c) 2014 marklarr 🌟. All rights reserved. // #import "fixtures.h" @implementation fixtures - (void)testedMethod { NSLog(@"tested"); } - (void)untestedMethod { NSLog(@"untested"); } @end OBJC expect(coverage_file.source_data).to eq(expected) end end describe "source_file_basename" do it "returns the base name of the source file" do expect(coverage_file.source_file_basename).to eq("fixtures") end end describe "source_file_pathname_relative_to_repo_root" do it "should return a pathname to the source_file, relative to the root of the repo" do expect(coverage_file.source_file_pathname_relative_to_repo_root).to eq(Pathname("spec/fixtures/fixtures/fixtures.m")) end end describe "#ignored" do it "should return true if the source_file_pathname globs against anything in the project.ignore_list" do coverage_file.project.ignore_list = ["*spec*", "*test*"] expect(coverage_file).to be_ignored end it "should return false if the source_file_pathname does not glob against anything in the project.ignore_list" do coverage_file.project.ignore_list = ["*test*", "*XCTest*"] expect(coverage_file).not_to be_ignored end end describe "gcov_data" do it "should process the gcno file with gcov and return the contents of the file" do expect(coverage_file.gcov_data).to include(": 15: NSLog(@\"tested\");") end end describe "line coverage" do before(:each) { allow(fixtures_project).to receive(:input_format).and_return("gcov") } let(:line_coverage_file) do fixtures_project.coverage_files.detect { |cf| cf.source_file_pathname.basename.to_s == "fixtures.m" } end describe "#coverage_for_line" do it "should return nil if the line is not relevant to coverage" do expect(line_coverage_file.coverage_for_line(" -: 75: }")).to be_nil end it "should return the number of times the line was executed if the line is relevant to coverage" do expect(line_coverage_file.coverage_for_line(" 1: 75: }")).to eq(1) expect(line_coverage_file.coverage_for_line(" 15: 75: }")).to eq(15) expect(line_coverage_file.coverage_for_line(" ####: 75: }")).to eq(0) end end describe "num_lines_tested" do it "should return the correct number of lines tested" do expect(line_coverage_file.num_lines_tested).to eq(3) end end describe "num_lines_testable" do it "should return the correct number of lines that are testable" do expect(line_coverage_file.num_lines_testable).to eq(6) end end describe "percentage_lines_tested" do it "should return the correct percentage of lines that are tested" do expect(line_coverage_file.percentage_lines_tested).to eq(50) end it "should return 100 if no testable lines" do allow(line_coverage_file).to receive(:num_lines_testable).and_return(0) expect(line_coverage_file.percentage_lines_tested).to eq(100) end end end describe "branch coverage" do let(:branch_coverage_file) do fixtures_project.coverage_files.detect { |cf| cf.source_file_pathname.basename.to_s == "Branches.m" } end describe "branch_coverage_data" do it "should return a hash with keys representing the line number of a branch statement" do expect(branch_coverage_file.branch_coverage_data.keys[0]).to eq(15) expect(branch_coverage_file.branch_coverage_data.keys[1]).to eq(18) expect(branch_coverage_file.branch_coverage_data.keys[2]).to eq(26) expect(branch_coverage_file.branch_coverage_data.keys[3]).to eq(29) end it "should store an array for each line number which contains the hit count for each branch" do data = branch_coverage_file.branch_coverage_data[15] expect(data.length).to eq(2) expect(data[0]).to be >= 1 expect(data[1]).to be >= 1 data = branch_coverage_file.branch_coverage_data[18] expect(data.length).to eq(2) expect(data[0]).to eq(0) expect(data[1]).to be >= 1 data = branch_coverage_file.branch_coverage_data[26] expect(data.length).to eq(2) expect(data[0]).to be >= 2 expect(data[1]).to eq(0) data = branch_coverage_file.branch_coverage_data[29] expect(data.length).to eq(4) expect(data[0]).to eq(0) expect(data[1]).to eq(0) expect(data[2]).to eq(0) expect(data[3]).to eq(0) end end describe "branch_coverage_data_for_statement_on_line" do it "return the array with branch hit counts for statement at a given line number" do data = branch_coverage_file.branch_coverage_data[15] expect(data.length).to eq(2) expect(data[0]).to be >= 1 expect(data[1]).to be >= 1 data = branch_coverage_file.branch_coverage_data[18] expect(data.length).to eq(2) expect(data[0]).to eq(0) expect(data[1]).to be >= 1 data = branch_coverage_file.branch_coverage_data[26] expect(data.length).to eq(2) expect(data[0]).to be >= 2 expect(data[1]).to eq(0) data = branch_coverage_file.branch_coverage_data[29] expect(data.length).to eq(4) expect(data[0]).to eq(0) expect(data[1]).to eq(0) expect(data[2]).to eq(0) expect(data[3]).to eq(0) end end describe "num_branch_hits_for_statement_on_line" do it "returns the number of branches executed below the statement at a given line number" do expect(branch_coverage_file.num_branch_hits_for_statement_on_line(15)).to eq(2) expect(branch_coverage_file.num_branch_hits_for_statement_on_line(18)).to eq(1) expect(branch_coverage_file.num_branch_hits_for_statement_on_line(26)).to eq(1) expect(branch_coverage_file.num_branch_hits_for_statement_on_line(29)).to eq(0) end end describe "rate_branch_coverage_for_statement_on_line" do it "returns the ratio between number of executed and number of total branches at a given line number" do expect(branch_coverage_file.rate_branch_coverage_for_statement_on_line(15)).to eq(1.0) expect(branch_coverage_file.rate_branch_coverage_for_statement_on_line(18)).to eq(0.5) expect(branch_coverage_file.rate_branch_coverage_for_statement_on_line(26)).to eq(0.5) expect(branch_coverage_file.rate_branch_coverage_for_statement_on_line(29)).to eq(0.0) end it "return 0.0 for a line without branch data" do expect(branch_coverage_file.rate_branch_coverage_for_statement_on_line(1)).to eq(0.0) end end describe "percentage_branch_coverage_for_statement_on_line" do it "returns the average hit percentage of all branches below the statement at a given line number" do expect(branch_coverage_file.percentage_branch_coverage_for_statement_on_line(15)).to eq(100) expect(branch_coverage_file.percentage_branch_coverage_for_statement_on_line(18)).to eq(50) expect(branch_coverage_file.percentage_branch_coverage_for_statement_on_line(26)).to eq(50) expect(branch_coverage_file.percentage_branch_coverage_for_statement_on_line(29)).to eq(0) end end describe "num_branches_testable" do it "returns the number of testable branches inside the class" do expect(branch_coverage_file.num_branches_testable).to eq(10) end end describe "num_branches_tested" do it "returns the number of tested branches inside the class" do expect(branch_coverage_file.num_branches_tested).to eq(4) end end describe "rate_branches_tested" do it "returns the ratio between tested and testable branches inside the class" do expect(branch_coverage_file.rate_branches_tested).to eq(0.4) end end end describe "empty coverage data" do let(:empty_file) do fixtures_project.coverage_files.detect { |cf| cf.source_file_pathname.basename.to_s == "Empty.m" } end describe "gcov_data" do it "returns an empty string" do gcov_data = empty_file.gcov_data expect(gcov_data).to be_empty end end describe "cleaned_gcov_data" do it "returns an empty string" do cleaned_gcov_data = empty_file.cleaned_gcov_data expect(cleaned_gcov_data).to be_empty end end describe "branch_coverage_data" do it "returns an empty hash for branch_coverage_data of an empty file" do data = empty_file.branch_coverage_data expect(data).to be_empty end end describe "num_branch_hits_for_statement_on_line" do it "returns 0.0 when no data is available" do expect(empty_file.num_branch_hits_for_statement_on_line(1)).to eq(0) end end describe "rate_branch_coverage_for_statement_on_line" do it "returns 0.0 when no data is available" do expect(empty_file.rate_branch_coverage_for_statement_on_line(1)).to eq(0.0) end end describe "percentage_branch_coverage_for_statement_on_line" do it "returns 0 when no data is available" do expect(empty_file.percentage_branch_coverage_for_statement_on_line(1)).to eq(0) end end describe "num_branches_testable" do it "returns 0 when no data is available" do expect(empty_file.num_branches_testable).to eq(0) end end describe "num_branches_tested" do it "returns 0 when no data is available" do expect(empty_file.num_branches_tested).to eq(0) end end describe "rate_branches_tested" do it "returns 0.0 when no data is available" do expect(empty_file.rate_branches_tested).to eq(0.0) end end end end ================================================ FILE: spec/slather/coverage_service/cobertura_xml_spec.rb ================================================ require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') require 'json' describe Slather::CoverageService::CoberturaXmlOutput do let(:fixtures_project) do proj = Slather::Project.open(FIXTURES_PROJECT_PATH) proj.build_directory = TEMP_DERIVED_DATA_PATH proj.input_format = "profdata" proj.coverage_service = "cobertura_xml" proj.configure proj end describe '#coverage_file_class' do it "should return CoverageFile" do expect(fixtures_project.send(:coverage_file_class)).to eq(Slather::ProfdataCoverageFile) end end describe '#post' do it "should create an XML report spanning all coverage files" do fixtures_project.post file = File.open(FIXTURES_XML_PATH) fixture_xml_doc = Nokogiri::XML(file) file.close file = File.open('cobertura.xml') current_xml_doc = Nokogiri::XML(file) file.close [current_xml_doc, fixture_xml_doc].each do |xml_doc| xml_doc.root['timestamp'] = '' xml_doc.root['version'] = '' source_node = xml_doc.at_css "source" source_node.content = '' end expect(EquivalentXml.equivalent?(current_xml_doc, fixture_xml_doc)).to be_truthy end it "should create an XML report in the given output directory" do fixtures_project.output_directory = "./output" fixtures_project.post filepath = "#{fixtures_project.output_directory}/cobertura.xml" expect(File.exist?(filepath)).to be_truthy FileUtils.rm_rf(fixtures_project.output_directory) end end end ================================================ FILE: spec/slather/coverage_service/coveralls_spec.rb ================================================ require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') describe Slather::CoverageService::Coveralls do let(:fixtures_project) do proj = Slather::Project.open(FIXTURES_PROJECT_PATH) proj.build_directory = TEMP_DERIVED_DATA_PATH proj.input_format = "profdata" proj.coverage_service = "coveralls" proj.send(:configure) proj end describe "#coverage_file_class" do it "should return CoverallsCoverageFile" do expect(fixtures_project.send(:coverage_file_class)).to eq(Slather::ProfdataCoverageFile) end end describe "#travis_job_id" do it "should return the TRAVIS_JOB_ID environment variable" do actual_travis_job_id = ENV['TRAVIS_JOB_ID'] ENV['TRAVIS_JOB_ID'] = "9182" expect(fixtures_project.send(:travis_job_id)).to eq("9182") ENV['TRAVIS_JOB_ID'] = actual_travis_job_id end end describe '#coveralls_coverage_data' do context "coverage_service is :travis_ci" do before(:each) { fixtures_project.ci_service = :travis_ci } it "should return valid json for coveralls coverage gcov data" do allow(fixtures_project).to receive(:travis_job_id).and_return("9182") expect(fixtures_project.send(:coveralls_coverage_data)).to be_json_eql("{\"service_job_id\":\"9182\",\"service_name\":\"travis-ci\"}").excluding("source_files") expect(fixtures_project.send(:coveralls_coverage_data)).to be_json_eql(fixtures_project.coverage_files.map(&:as_json).to_json).at_path("source_files") end it "should raise an error if there is no TRAVIS_JOB_ID" do allow(fixtures_project).to receive(:travis_job_id).and_return(nil) expect { fixtures_project.send(:coveralls_coverage_data) }.to raise_error(StandardError) end it "should raise an error if there is a COVERAGE_ACCESS_TOKEN" do allow(fixtures_project).to receive(:coverage_access_token).and_return("abc123") expect { fixtures_project.send(:coveralls_coverage_data) }.to raise_error(StandardError) end end context "coverage_service is :travis_pro" do before(:each) { fixtures_project.ci_service = :travis_pro } it "should return valid json for coveralls coverage data" do allow(fixtures_project).to receive(:travis_job_id).and_return("9182") allow(fixtures_project).to receive(:coverage_access_token).and_return("abc123") expect(fixtures_project.send(:coveralls_coverage_data)).to be_json_eql("{\"service_job_id\":\"9182\",\"service_name\":\"travis-pro\",\"repo_token\":\"abc123\"}").excluding("source_files") expect(fixtures_project.send(:coveralls_coverage_data)).to be_json_eql(fixtures_project.coverage_files.map(&:as_json).to_json).at_path("source_files") end it "should raise an error if there is no TRAVIS_JOB_ID" do allow(fixtures_project).to receive(:travis_job_id).and_return(nil) expect { fixtures_project.send(:coveralls_coverage_data) }.to raise_error(StandardError) end it "should raise an error if there is no COVERAGE_ACCESS_TOKEN" do allow(fixtures_project).to receive(:coverage_access_token).and_return("") expect { fixtures_project.send(:coveralls_coverage_data) }.to raise_error(StandardError) end end context "coverage_service is :circleci" do before(:each) { fixtures_project.ci_service = :circleci } it "should return valid json for coveralls coverage data" do allow(fixtures_project).to receive(:circleci_job_id).and_return("9182") allow(fixtures_project).to receive(:coverage_access_token).and_return("abc123") allow(fixtures_project).to receive(:circleci_pull_request).and_return("1") allow(fixtures_project).to receive(:circleci_build_url).and_return("https://circleci.com/gh/Bruce/Wayne/1") allow(fixtures_project).to receive(:circleci_git_info).and_return({ :head => { :id => "ababa123", :author_name => "bwayne", :message => "hello" }, :branch => "master" }) expect(fixtures_project.send(:coveralls_coverage_data)).to be_json_eql("{\"service_job_id\":\"9182\",\"service_name\":\"circleci\",\"repo_token\":\"abc123\",\"service_pull_request\":\"1\",\"service_build_url\":\"https://circleci.com/gh/Bruce/Wayne/1\",\"git\":{\"head\":{\"id\":\"ababa123\",\"author_name\":\"bwayne\",\"message\":\"hello\"},\"branch\":\"master\"}}").excluding("source_files") expect(fixtures_project.send(:coveralls_coverage_data)).to be_json_eql(fixtures_project.coverage_files.map(&:as_json).to_json).at_path("source_files") end it "should raise an error if there is no CIRCLE_BUILD_NUM" do allow(fixtures_project).to receive(:circleci_job_id).and_return(nil) expect { fixtures_project.send(:coveralls_coverage_data) }.to raise_error(StandardError) end end context "coverage_service is :jenkins" do before(:each) { fixtures_project.ci_service = :jenkins } it "should return valid json for coveralls coverage data" do allow(fixtures_project).to receive(:jenkins_job_id).and_return("9182") allow(fixtures_project).to receive(:coverage_access_token).and_return("abc123") allow(fixtures_project).to receive(:jenkins_git_info).and_return({head: {id: "master", author_name: "author", message: "pull title" }, branch: "branch"}) allow(fixtures_project).to receive(:jenkins_branch_name).and_return('master') expect(fixtures_project.send(:coveralls_coverage_data)).to be_json_eql("{\"service_job_id\":\"9182\",\"service_name\":\"jenkins\",\"repo_token\":\"abc123\",\"git\":{\"head\":{\"id\":\"master\",\"author_name\":\"author\",\"message\":\"pull title\"},\"branch\":\"branch\"}}").excluding("source_files") expect(fixtures_project.send(:coveralls_coverage_data)).to be_json_eql(fixtures_project.coverage_files.map(&:as_json).to_json).at_path("source_files") end it "should raise an error if there is no BUILD_ID" do allow(fixtures_project).to receive(:jenkins_job_id).and_return(nil) expect { fixtures_project.send(:coveralls_coverage_data) }.to raise_error(StandardError) end end it "should raise an error if it does not recognize the ci_service" do fixtures_project.ci_service = :jenkins_ci expect { fixtures_project.send(:coveralls_coverage_data) }.to raise_error(StandardError) end context "coverage_service is :teamcity" do before(:each) { fixtures_project.ci_service = :teamcity } it "should return valid json for coveralls coverage data" do allow(fixtures_project).to receive(:teamcity_job_id).and_return("9182") allow(fixtures_project).to receive(:coverage_access_token).and_return("abc123") allow(fixtures_project).to receive(:teamcity_git_info).and_return({head: {id: "master", author_name: "author", author_email: "john.doe@slather.com", message: "pull title" }, branch: "branch"}) allow(fixtures_project).to receive(:teamcity_branch_name).and_return('master') expect(fixtures_project.send(:coveralls_coverage_data)).to be_json_eql("{\"service_job_id\":\"9182\",\"service_name\":\"teamcity\",\"repo_token\":\"abc123\",\"git\":{\"head\":{\"id\":\"master\",\"author_name\":\"author\",\"author_email\":\"john.doe@slather.com\",\"message\":\"pull title\"},\"branch\":\"branch\"}}").excluding("source_files") expect(fixtures_project.send(:coveralls_coverage_data)).to be_json_eql(fixtures_project.coverage_files.map(&:as_json).to_json).at_path("source_files") end it "should raise an error if there is no TC_BUILD_NUMBER" do allow(fixtures_project).to receive(:teamcity_job_id).and_return(nil) expect { fixtures_project.send(:coveralls_coverage_data) }.to raise_error(StandardError) end it "should return the teamcity branch name" do git_branch = ENV['GIT_BRANCH'] ENV['GIT_BRANCH'] = "master" expect(fixtures_project.send(:teamcity_branch_name)).to eq("master") ENV['GIT_BRANCH'] = git_branch end it "should return the teamcity job id" do teamcity_job_id = ENV['TC_BUILD_NUMBER'] ENV['TC_BUILD_NUMBER'] = "9182" expect(fixtures_project.send(:teamcity_job_id)).to eq("9182") ENV['TC_BUILD_NUMBER'] = teamcity_job_id end it "should return teamcity git info" do git_branch = ENV['GIT_BRANCH'] ENV['GIT_BRANCH'] = "master" expect(fixtures_project.send(:teamcity_git_info)).to eq({head: {id: `git log --format=%H -n 1 HEAD`.chomp, author_name: `git log --format=%an -n 1 HEAD`.chomp, author_email: `git log --format=%ae -n 1 HEAD`.chomp, message: `git log --format=%s -n 1 HEAD`.chomp }, branch: "master"}) ENV['GIT_BRANCH'] = git_branch end end context "coverage_service is :github" do before(:each) { fixtures_project.ci_service = :github } it "should return valid json for coveralls coverage data" do allow(fixtures_project).to receive(:github_job_id).and_return("9182") allow(fixtures_project).to receive(:coverage_access_token).and_return("abc123") allow(fixtures_project).to receive(:github_pull_request).and_return("1") allow(fixtures_project).to receive(:github_build_url).and_return("https://github.com/Bruce/Wayne/actions/runs/1") allow(fixtures_project).to receive(:github_git_info).and_return({ :head => { :id => "ababa123", :author_name => "bwayne", :message => "hello" }, :branch => "master" }) expect(fixtures_project.send(:coveralls_coverage_data)).to be_json_eql("{\"service_job_id\":\"9182\",\"service_name\":\"github\",\"repo_name\":\"\",\"repo_token\":\"abc123\",\"service_pull_request\":\"1\",\"service_build_url\":\"https://github.com/Bruce/Wayne/actions/runs/1\",\"git\":{\"head\":{\"id\":\"ababa123\",\"author_name\":\"bwayne\",\"message\":\"hello\"},\"branch\":\"master\"}}").excluding("source_files") expect(fixtures_project.send(:coveralls_coverage_data)).to be_json_eql(fixtures_project.coverage_files.map(&:as_json).to_json).at_path("source_files") end it "should raise an error if there is no GITHUB_RUN_ID" do allow(fixtures_project).to receive(:github_job_id).and_return(nil) expect { fixtures_project.send(:coveralls_coverage_data) }.to raise_error(StandardError) end end end describe '#coveralls_coverage_data' do context "coverage_service is :travis_ci" do before(:each) { fixtures_project.ci_service = :travis_ci project_root = Pathname("./").realpath allow(fixtures_project).to receive(:llvm_cov_export_output).and_return(%q( { "data":[ { "files":[] } ] } )) allow(fixtures_project).to receive(:profdata_llvm_cov_output).and_return("#{project_root}/spec/fixtures/fixtures/fixtures.m: | 1|// | 2|// fixtures.m | 3|// fixtures | 4|// | 5|// Created by Mark Larsen on 6/24/14. | 6|// Copyright (c) 2014 marklarr. All rights reserved. | 7|// | 8| | 9|#import \"fixtures.h\" | 10| | 11|@implementation fixtures | 12| | 13|- (void)testedMethod 1| 14|{ 1| 15| NSLog(@\"tested\"); 1| 16|} | 17| | 18|- (void)untestedMethod 0| 19|{ 0| 20| NSLog(@\"untested\"); 0| 21|} | 22| | 23|@end ") } it "should save the coveralls_coverage_data to a file and post it to coveralls" do allow(fixtures_project).to receive(:travis_job_id).and_return("9182") expect(fixtures_project).to receive(:`) do |cmd| expect(cmd).to eq("curl -s --form json_file=@coveralls_json_file https://coveralls.io/api/v1/jobs") expect(File.read('coveralls_json_file')).to be_json_eql(fixtures_project.send(:coveralls_coverage_data)) end.once fixtures_project.post end it "should save the coveralls_coverage_data to a file, post it to coveralls, and fail due to a Coveralls error" do allow(fixtures_project).to receive(:travis_job_id).and_return("9182") allow(fixtures_project).to receive(:`).and_return("{\"message\":\"Couldn't find a repository matching this job.\",\"error\":true}") expect(fixtures_project).to receive(:`).once expect { fixtures_project.post }.to raise_error(StandardError) end it "should always remove the coveralls_json_file after it's done" do allow(fixtures_project).to receive(:`) allow(fixtures_project).to receive(:travis_job_id).and_return("9182") fixtures_project.post expect(File.exist?("coveralls_json_file")).to be_falsy allow(fixtures_project).to receive(:travis_job_id).and_return(nil) expect { fixtures_project.post }.to raise_error(StandardError) expect(File.exist?("coveralls_json_file")).to be_falsy end it "should return valid json for coveralls coverage profdata data" do allow(fixtures_project).to receive(:travis_job_id).and_return("9182") expect(fixtures_project.send(:coveralls_coverage_data)).to be_json_eql("{\"service_job_id\":\"9182\",\"service_name\":\"travis-ci\"}").excluding("source_files") expect(fixtures_project.send(:coveralls_coverage_data)).to be_json_eql(fixtures_project.coverage_files.map(&:as_json).to_json).at_path("source_files") end end end end ================================================ FILE: spec/slather/coverage_service/gutter_json_spec.rb ================================================ require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') require 'json' describe Slather::CoverageService::GutterJsonOutput do let(:fixtures_project) do proj = Slather::Project.open(FIXTURES_PROJECT_PATH) proj.build_directory = TEMP_DERIVED_DATA_PATH proj.input_format = "profdata" proj.coverage_service = "gutter_json" proj.configure proj end describe '#coverage_file_class' do it "should return CoverageFile" do expect(fixtures_project.send(:coverage_file_class)).to eq(Slather::ProfdataCoverageFile) end end describe '#post' do it "should print out the coverage for each file, and then total coverage" do fixtures_project.post fixture_json = File.read(FIXTURES_GUTTER_JSON_PATH) current_json = File.read('.gutter.json') expect(current_json).to be_json_eql(fixture_json) File.unlink('.gutter.json') end end end ================================================ FILE: spec/slather/coverage_service/hardcover_spec.rb ================================================ require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') describe Slather::CoverageService::Hardcover do let(:fixtures_project) do proj = Slather::Project.open(FIXTURES_PROJECT_PATH) proj.build_directory = TEMP_DERIVED_DATA_PATH proj.input_format = "profdata" proj.coverage_service = "hardcover" proj.configure proj end let(:fixture_yaml) do yaml_text = <<-EOF hardcover_repo_token: "27dd855e706b22126ec6daaaf7bb40b5" hardcover_base_url: "http://api.hardcover.io" EOF yaml = YAML.load(yaml_text) end describe "#coverage_file_class" do it "should return CoverallsCoverageFile" do expect(fixtures_project.send(:coverage_file_class)).to eq(Slather::ProfdataCoverageFile) end end describe "#job_id" do it "should return the Jenkins JOB_NAME and BUILD_NUMBER environment variables" do ENV['BUILD_NUMBER'] = "9182" ENV['JOB_NAME'] = "slather-master" expect(fixtures_project.send(:jenkins_job_id)).to eq("slather-master/9182") end end describe '#hardcover_coverage_data' do context "coverage_service is :jenkins_ci" do before(:each) do fixtures_project.ci_service = :jenkins_ci allow(Slather::Project).to receive(:yml).and_return(fixture_yaml) end it "should return a valid json" do json = JSON(fixtures_project.send(:hardcover_coverage_data)) expect(json["service_job_id"]).to eq("slather-master/9182") expect(json["service_name"]).to eq("jenkins-ci") expect(json["repo_token"]).to eq("27dd855e706b22126ec6daaaf7bb40b5") expect(json["source_files"]).to_not be_empty end it "should raise an error if there is no BUILD_NUMBER or JOB_NAME" do allow(fixtures_project).to receive(:jenkins_job_id).and_return(nil) expect { fixtures_project.send(:hardcover_coverage_data) }.to raise_error(StandardError) end end it "should raise an error if it does not recognize the ci_service" do fixtures_project.ci_service = :non_existing_ci expect { fixtures_project.send(:hardcover_coverage_data) }.to raise_error(StandardError) end end describe '#post' do before(:each) do allow(Slather::Project).to receive(:yml).and_return(fixture_yaml) fixtures_project.ci_service = :jenkins_ci project_root = Pathname("./").realpath allow(fixtures_project).to receive(:llvm_cov_export_output).and_return(%q( { "data":[ { "files":[] } ] } )) allow(fixtures_project).to receive(:profdata_llvm_cov_output).and_return("#{project_root}/spec/fixtures/fixtures/fixtures.m: | 1|// | 2|// fixtures.m | 3|// fixtures | 4|// | 5|// Created by Mark Larsen on 6/24/14. | 6|// Copyright (c) 2014 marklarr. All rights reserved. | 7|// | 8| | 9|#import \"fixtures.h\" | 10| | 11|@implementation fixtures | 12| | 13|- (void)testedMethod 1| 14|{ 1| 15| NSLog(@\"tested\"); 1| 16|} | 17| | 18|- (void)untestedMethod 0| 19|{ 0| 20| NSLog(@\"untested\"); 0| 21|} | 22| | 23|@end ") end it "should save the hardcover_coverage_data to a file and post it to hardcover" do allow(fixtures_project).to receive(:jenkins_job_id).and_return("slather-master/9182") allow(fixtures_project).to receive(:coverage_service_url).and_return("http://api.hardcover.io") expect(fixtures_project).to receive(:`) do |cmd| expect(cmd).to eq("curl --form json_file=@hardcover_json_file http://api.hardcover.io/v1/jobs") end.once fixtures_project.post end it "should always remove the hardcover_json_file after it's done" do allow(fixtures_project).to receive(:`) allow(fixtures_project).to receive(:jenkins_job_id).and_return("slather-master/9182") allow(fixtures_project).to receive(:coverage_service_url).and_return("http://api.hardcover.io") fixtures_project.post expect(File.exist?("hardcover_json_file")).to be_falsy allow(fixtures_project).to receive(:jenkins_job_id).and_return(nil) expect { fixtures_project.post }.to raise_error(StandardError) expect(File.exist?("hardcover_json_file")).to be_falsy end end end ================================================ FILE: spec/slather/coverage_service/html_output_spec.rb ================================================ require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') require 'nokogiri' describe Slather::CoverageService::HtmlOutput do OUTPUT_DIR_PATH = "html" let(:fixture_html_files) do ["index", "fixtures.m", "Branches.m", "fixturesTests.m", "peekaviewTests💣.m", "BranchesTests.m"].map { |file| "#{file}.html"} end let(:fixtures_project) do proj = Slather::Project.open(FIXTURES_PROJECT_PATH) proj.build_directory = TEMP_DERIVED_DATA_PATH proj.input_format = "profdata" proj.coverage_service = "html" proj end describe '#coverage_file_class' do it "should return CoverageFile" do expect(fixtures_project.send(:coverage_file_class)).to eq(Slather::ProfdataCoverageFile) end it "should allow accessing docs via attribute" do expect(fixtures_project.docs).to eq(nil) end end describe '#post' do before(:each) { allow(fixtures_project).to receive(:print_path_coverage) fixtures_project.send(:configure) } after(:each) { FileUtils.rm_rf(OUTPUT_DIR_PATH) if Dir.exist?(OUTPUT_DIR_PATH) } def extract_header_title(doc) doc.at_css('title').text end it "should create all coverage as static html files" do fixtures_project.post fixture_html_files.map { |filename| File.join(OUTPUT_DIR_PATH, filename) }.each { |filepath| expect(File.exist?(filepath)).to be_truthy } end it "should print out the path of the html folder by default" do fixtures_project.post expect(fixtures_project).to have_received(:print_path_coverage).with("html/index.html") end it "should open the index.html automatically if --show is flagged" do allow(fixtures_project).to receive(:open_coverage) fixtures_project.show_html = true fixtures_project.post expect(fixtures_project).to have_received(:open_coverage).with("html/index.html") end it "should create index html with correct coverage information" do def extract_title(doc) doc.at_css('#reports > h2').text end def extract_coverage_text(doc) doc.at_css('#total_coverage').text end def extract_coverage_class(doc) doc.at_css('#total_coverage').attribute("class").to_s end def extract_cov_header(doc) doc.css("table.coverage_list > thead > tr > th").map { |header| [header.text, header.attribute("data-sort")].join(", ") }.join("; ") end def extract_cov_index(doc) coverages = doc.css("table.coverage_list > tbody > tr").map { |tr| tr.css("td").map { |td| if td.attribute("class") td.attribute("class").to_s.split.join(", ") + ", #{td.text}" elsif span = td.at_css("span") span.attribute("class").to_s.split.join(", ") + ", #{td.text}" else td.text end }.join("; ") } list = doc.css("table.coverage_list > tbody").attribute("class") coverages.append(list.to_s) end fixtures_project.post file = File.open(File.join(FIXTURES_HTML_FOLDER_PATH, "index.html")) fixture_doc = Nokogiri::HTML(file) file.close file = File.open(File.join(OUTPUT_DIR_PATH, "index.html")) current_doc = Nokogiri::HTML(file) file.close expect(extract_header_title(current_doc)).to eq(extract_header_title(fixture_doc)) expect(extract_title(current_doc)).to eq(extract_title(fixture_doc)) expect(extract_coverage_text(current_doc)).to eq(extract_coverage_text(fixture_doc)) expect(extract_coverage_class(current_doc)).to eq(extract_coverage_class(fixture_doc)) expect(extract_cov_header(current_doc)).to eq(extract_cov_header(fixture_doc)) expect(extract_cov_index(current_doc)).to eq(extract_cov_index(fixture_doc)) end it "should create html coverage for each file with correct coverage" do def extract_title(doc) doc.css('#coverage > h2 > span').map{ |x| x.text.strip }.join(", ") end def extract_subtitle(doc) (sub = doc.at_css('h4.cov_subtitle')) ? sub.text : "" end def extract_filepath(doc) (path = doc.at_css('h4.cov_filepath'))? path.text : "" end def extract_cov_data(doc) doc.css("table.source_code > tr").map { |tr| ([tr.attribute("class")] + tr.css('td').map(&:text)).join(",") } end fixtures_project.post fixture_html_files.each { |filename| file = File.open(File.join(FIXTURES_HTML_FOLDER_PATH, filename)) fixture_doc = Nokogiri::HTML(file) file.close file = File.open(File.join(OUTPUT_DIR_PATH, filename)) current_doc = Nokogiri::HTML(file) file.close expect(extract_title(fixture_doc)).to eq(extract_title(current_doc)) expect(extract_subtitle(fixture_doc)).to eq(extract_subtitle(current_doc)) expect(extract_filepath(fixture_doc)).to eq(extract_filepath(current_doc)) expect(extract_cov_data(fixture_doc)).to eq(extract_cov_data(current_doc)) } end it "should create an HTML report directory in the given output directory" do fixtures_project.output_directory = "./output" fixtures_project.post expect(Dir.exist?(fixtures_project.output_directory)).to be_truthy FileUtils.rm_rf(fixtures_project.output_directory) if Dir.exist?(fixtures_project.output_directory) end it "should create the default directory (html) if output directory is faulty" do fixtures_project.output_directory = " " fixtures_project.post expect(Dir.exist?(OUTPUT_DIR_PATH)).to be_truthy end it "should create a valid report when using profdata format" do def extract_filepath(doc) (path = doc.at_css('h4.cov_filepath'))? path.text : "" end allow(fixtures_project).to receive(:input_format).and_return("profdata") allow(fixtures_project).to receive(:pathnames_per_binary).and_return([{"filename" => Pathname.new("./spec/fixtures/fixtures/other_fixtures.m"), "segments" => []}]) allow(fixtures_project).to receive(:profdata_llvm_cov_output).and_return("1| |// 2| |// other_fixtures.m 3| |// fixtures 4| |// 5| |// Created by Mark Larsen on 6/24/14. 6| |// Copyright (c) 2014 marklarr. All rights reserved. 7| |// 8| | 9| |#import \"other_fixtures.h\" 10| | 11| |@implementation other_fixtures 12| | 13| |- (void)testedMethod 14| 1|{ 15| 1| NSLog(@\"tested\"); 16| 1|} 17| | 18| |- (void)untestedMethod 19| 0|{ 20| 0| NSLog(@\"untested\"); 21| 0|} 22| | 23| |@end ") fixtures_project.post file = File.open(File.join(OUTPUT_DIR_PATH, "other_fixtures.m.html")) doc = Nokogiri::HTML(file) file.close expect(extract_header_title(doc)).to eq("other_fixtures.m - Slather") expect(extract_filepath(doc)).to eq("spec/fixtures/fixtures/other_fixtures.m") end end end ================================================ FILE: spec/slather/coverage_service/json_spec.rb ================================================ require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') require 'json' describe Slather::CoverageService::JsonOutput do let(:fixtures_project) do proj = Slather::Project.open(FIXTURES_PROJECT_PATH) proj.build_directory = TEMP_DERIVED_DATA_PATH proj.binary_basename = ["fixturesTests", "libfixturesTwo"] proj.input_format = "profdata" proj.coverage_service = "json" proj.configure proj end describe '#coverage_file_class' do it "should return ProfdataCoverageFile" do expect(fixtures_project.send(:coverage_file_class)).to eq(Slather::ProfdataCoverageFile) end end describe '#post' do it "should create an JSON report spanning all coverage files" do fixtures_project.post output_json = JSON.parse(File.read('report.json')) fixture_json = JSON.parse(File.read(FIXTURES_JSON_PATH)) expect(output_json).to eq(fixture_json) FileUtils.rm('report.json') end it "should create an JSON report in the given output directory" do fixtures_project.output_directory = "./output" fixtures_project.post filepath = "#{fixtures_project.output_directory}/report.json" expect(File.exist?(filepath)).to be_truthy FileUtils.rm_rf(fixtures_project.output_directory) end end end ================================================ FILE: spec/slather/coverage_service/llvm_cov_spec.rb ================================================ require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') describe Slather::CoverageService::LlvmCovOutput do let(:fixtures_project) do proj = Slather::Project.open(FIXTURES_PROJECT_PATH) proj.build_directory = TEMP_DERIVED_DATA_PATH proj.binary_basename = ["fixturesTests", "libfixturesTwo"] proj.input_format = "profdata" proj.coverage_service = "llvm_cov" proj.configure proj end describe '#coverage_file_class' do it "should return ProfdataCoverageFile" do expect(fixtures_project.send(:coverage_file_class)).to eq(Slather::ProfdataCoverageFile) end end describe '#post' do it "should create an llvm-cov report spanning all coverage files" do fixtures_project.post output_llcov = File.read('report.llcov') fixture_llcov = File.read(FIXTURES_LLCOV_PATH) output_llcov, fixture_llcov = [output_llcov, fixture_llcov].map do |llcov_doc| llcov_doc.gsub(/^\/.+:$/, '') end expect(output_llcov).to eq(fixture_llcov) FileUtils.rm('report.llcov') end it "should create an llvm-cov report in the given output directory" do fixtures_project.output_directory = "./output" fixtures_project.post filepath = "#{fixtures_project.output_directory}/report.llcov" expect(File.exist?(filepath)).to be_truthy FileUtils.rm_rf(fixtures_project.output_directory) end end end ================================================ FILE: spec/slather/coverage_service/simple_output_spec.rb ================================================ require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') describe Slather::CoverageService::SimpleOutput do let(:fixtures_project) do proj = Slather::Project.open(FIXTURES_PROJECT_PATH) proj.build_directory = TEMP_DERIVED_DATA_PATH proj.binary_basename = ["fixturesTests", "libfixturesTwo"] proj.input_format = "profdata" proj.configure proj end describe '#coverage_file_class' do it "should return CoverageFile" do expect(fixtures_project.send(:coverage_file_class)).to eq(Slather::ProfdataCoverageFile) end end describe '#post' do it "should print out the coverage for each file, and then total coverage" do ["spec/fixtures/fixtures/fixtures.m: 3 of 6 lines (50.00%)", "spec/fixtures/fixtures/more_files/Branches.m: 13 of 30 lines (43.33%)", "spec/fixtures/fixturesTests/BranchesTests.m: 16 of 16 lines (100.00%)", "spec/fixtures/fixturesTests/fixturesTests.m: 17 of 17 lines (100.00%)", "spec/fixtures/fixturesTests/peekaviewTests💣.m: 11 of 11 lines (100.00%)", "spec/fixtures/fixturesTwo/fixturesTwo.m: 6 of 6 lines (100.00%)", "Tested 66/86 statements", "Test Coverage: 76.74%" ].each do |line| expect(fixtures_project).to receive(:puts).with(line) end fixtures_project.post end describe 'ci_service reporting output' do context "ci_service is :teamcity" do before(:each) { fixtures_project.ci_service = :teamcity } it "should print out the coverage" do ["spec/fixtures/fixtures/fixtures.m: 3 of 6 lines (50.00%)", "spec/fixtures/fixtures/more_files/Branches.m: 13 of 30 lines (43.33%)", "spec/fixtures/fixturesTests/BranchesTests.m: 16 of 16 lines (100.00%)", "spec/fixtures/fixturesTests/fixturesTests.m: 17 of 17 lines (100.00%)", "spec/fixtures/fixturesTests/peekaviewTests💣.m: 11 of 11 lines (100.00%)", "spec/fixtures/fixturesTwo/fixturesTwo.m: 6 of 6 lines (100.00%)", "##teamcity[buildStatisticValue key='CodeCoverageAbsLCovered' value='66']", "##teamcity[buildStatisticValue key='CodeCoverageAbsLTotal' value='86']", "Tested 66/86 statements", "Test Coverage: 76.74%" ].each do |line| expect(fixtures_project).to receive(:puts).with(line) end fixtures_project.post end end end end end ================================================ FILE: spec/slather/coverage_service/sonarqube_xml_spec.rb ================================================ require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') require 'json' describe Slather::CoverageService::SonarqubeXmlOutput do let(:fixtures_project) do proj = Slather::Project.open(FIXTURES_PROJECT_PATH) proj.build_directory = TEMP_DERIVED_DATA_PATH proj.input_format = "profdata" proj.coverage_service = "sonarqube_xml" proj.configure proj end describe '#coverage_file_class' do it "should return CoverageFile" do expect(fixtures_project.send(:coverage_file_class)).to eq(Slather::ProfdataCoverageFile) end end describe '#post' do it "should create an XML report spanning all coverage files" do fixtures_project.post file = File.open(FIXTURES_SONARQUBE_XML_PATH) fixture_xml_doc = Nokogiri::XML(file) file.close file = File.open('sonarqube-generic-coverage.xml') current_xml_doc = Nokogiri::XML(file) file.close expect(EquivalentXml.equivalent?(current_xml_doc, fixture_xml_doc)).to be_truthy end it "should create an XML report in the given output directory" do fixtures_project.output_directory = "./output" fixtures_project.post filepath = "#{fixtures_project.output_directory}/sonarqube-generic-coverage.xml" expect(File.exist?(filepath)).to be_truthy FileUtils.rm_rf(fixtures_project.output_directory) end end end ================================================ FILE: spec/slather/profdata_coverage_spec.rb ================================================ require File.join(File.dirname(__FILE__), '..', 'spec_helper') describe Slather::ProfdataCoverageFile do let(:fixtures_project) do Slather::Project.open(FIXTURES_PROJECT_PATH) end let(:profdata_coverage_file) do Slather::ProfdataCoverageFile.new(fixtures_project, "/Users/venmo/ExampleProject/AppDelegate.swift: | 1|// | 2|// AppDelegate.swift | 3|// xcode7workbench01 | 4|// | 5|// Created by Simone Civetta on 08/06/15. | 6|// Copyright © 2015 Xebia IT Architects. All rights reserved. | 7|// | 8| | 9|import UIKit | 10| | 11|@UIApplicationMain | 12|class AppDelegate: UIResponder, UIApplicationDelegate { | 13| | 14| var window: UIWindow? | 15| | 16| 1| 17| func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 1| 18| // Override point for customization after application launch. 1| 19| return true 1| 20| } | 21| 0| 22| func applicationWillResignActive(application: UIApplication) { 0| 23| // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 0| 24| // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 0| 25| } | 26| 0| 27| func applicationDidEnterBackground(application: UIApplication) { 0| 28| // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 0| 29| // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 0| 30| } | 31| 0| 32| func applicationWillEnterForeground(application: UIApplication) { 0| 33| // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 0| 34| } | 35| 1| 36| func applicationDidBecomeActive(application: UIApplication) { 0| 37| // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 0| 38| } | 39| 0| 40| func applicationWillTerminate(application: UIApplication) { 0| 41| } | 42| | 43| | 44|} | 45|", false) end describe "#initialize" do it "should create a list of lines" do expect(profdata_coverage_file.line_data.length).to eq(45) expect(profdata_coverage_file.line_data[9]).to eq(" | 9|import UIKit") end end describe "#all_lines" do it "should return a list of all the lines" do expect(profdata_coverage_file.all_lines.length).to eq(45) expect(profdata_coverage_file.all_lines[8]).to eq(" | 9|import UIKit") end end describe "#line_number_in_line" do it "should return the correct line number for coverage represented as decimals" do expect(profdata_coverage_file.line_number_in_line(" 0| 40| func applicationWillTerminate(application: UIApplication) {", false)).to eq(40) end it "should return the correct line number for coverage represented as thousands" do expect(profdata_coverage_file.line_number_in_line(" 11.8k| 41| func applicationWillTerminate(application: UIApplication) {", false)).to eq(41) end it "should return the correct line number for coverage represented as milions" do expect(profdata_coverage_file.line_number_in_line(" 2.58M| 42| func applicationWillTerminate(application: UIApplication) {", false)).to eq(42) end it "should handle line numbers first" do expect(profdata_coverage_file.line_number_in_line(" 18| 1| return a + b;", true)).to eq(18) expect(profdata_coverage_file.line_number_in_line(" 18| 2.58M| return a + b;", true)).to eq(18) expect(profdata_coverage_file.line_number_in_line(" 18| 11.8k| return a + b;", true)).to eq(18) expect(profdata_coverage_file.line_number_in_line(" 4| |//", true)).to eq(4) end end describe '#ignore_error_lines' do it 'should ignore lines starting with - when line_numbers_first is true' do expect(profdata_coverage_file.ignore_error_lines(['--------'], true)).to eq([]) expect(profdata_coverage_file.ignore_error_lines(['--------', 'not ignored'], true)).to eq(['not ignored']) end it 'should ignore lines starting with | when line_numbers_first is true' do expect(profdata_coverage_file.ignore_error_lines(['| Unexecuted instantiation'], true)).to eq([]) expect(profdata_coverage_file.ignore_error_lines(['| Unexecuted instantiation', 'not ignored'], true)).to eq(['not ignored']) end it 'should not ignore any lines when line_numbers_first is false' do lines = ['| Unexecuted instantiation', '------'] expect(profdata_coverage_file.ignore_error_lines(lines, false)).to eq(lines) end end describe "#coverage_for_line" do it "should return the number of hits for the line" do expect(profdata_coverage_file.coverage_for_line(" 10| 40| func applicationWillTerminate(application: UIApplication) {", false)).to eq(10) end it "should return the number of hits for a line in thousands as an integer" do result = profdata_coverage_file.coverage_for_line(" 11.8k| 49| return result;", false) expect(result).to eq(11800) expect(result).to be_a(Integer) end it "should return the number of hits for a line in millions as an integer" do result = profdata_coverage_file.coverage_for_line(" 2.58M| 49| return result;", false) expect(result).to eq(2580000) expect(result).to be_a(Integer) end it "should return the number of hits for an uncovered line" do expect(profdata_coverage_file.coverage_for_line(" 0| 49| return result;", false)).to eq(0) end it "should handle line numbers first" do expect(profdata_coverage_file.coverage_for_line(" 18| 1| return a + b;", true)).to eq(1) expect(profdata_coverage_file.coverage_for_line(" 18| 11.8k| return a + b;", true)).to eq(11800) expect(profdata_coverage_file.coverage_for_line(" 18| 2.58M| return a + b;", true)).to eq(2580000) end it 'should ignore errors in profdata' do expect(profdata_coverage_file.coverage_for_line('------------------', true)).to eq(nil) end end describe "#num_lines_tested" do it "should count the actual number of line tested" do expect(profdata_coverage_file.num_lines_tested).to eq(5) end end describe "#num_lines_testable" do it "should count the actual testable number of line" do expect(profdata_coverage_file.num_lines_testable).to eq(20) end end describe "#percentage_line_tested" do it "should count the percentage of tested lines" do expect(profdata_coverage_file.percentage_lines_tested).to eq(25) end end describe "#branch_coverage_data" do it "should have branch data for line 19" do # these segments correspond to the only statement on line 19 profdata_coverage_file.segments = [[19, 9, 0, true, false], [19, 20, 1, true, false]] expect(profdata_coverage_file.branch_coverage_data[19]).to eq([0,1]) end it "should have missing region data for line 19" do profdata_coverage_file.segments = [[19, 9, 0, true, false], [19, 20, 1, true, false]] expect(profdata_coverage_file.branch_region_data[19]).to eq([[8,11]]) end it "should have two missing region data for line 19" do profdata_coverage_file.segments = [[19, 9, 0, true, false], [19, 20, 1, true, false], [19, 30, 0, true, false], [19, 40, 1, true, true]] expect(profdata_coverage_file.branch_region_data[19]).to eq([[8,11], [29,10]]) end end describe "#ignored" do before(:each) { allow(fixtures_project).to receive(:ignore_list).and_return([]) } it "shouldn't ignore project files" do ignorable_file = Slather::ProfdataCoverageFile.new(fixtures_project, "/Users/venmo/ExampleProject/AppDelegate.swift: | 1|// | 2|// AppDelegate.swift | 3|// xcode7workbench01", false) expect(ignorable_file.ignored?).to be_falsy end it "should ignore platform files" do ignorable_file = Slather::ProfdataCoverageFile.new(fixtures_project, "../../../../../../../../Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks/XCTest.framework/Headers/XCTestAssertionsImpl.h: | 1|// | 2|// AppDelegate.swift | 3|// xcode7workbench01", false) expect(ignorable_file.ignored?).to be_truthy end it "should ignore warnings" do ignorable_file = Slather::ProfdataCoverageFile.new(fixtures_project, "warning The file '/Users/ci/.ccache/tmp/CALayer-KI.stdout.macmini08.92540.QAQaxt.mi' isn't covered.", false) expect(ignorable_file.ignored?).to be_truthy end end end ================================================ FILE: spec/slather/project_spec.rb ================================================ require File.join(File.dirname(__FILE__), '..', 'spec_helper') describe Slather::Project do FIXTURES_PROJECT_SETUP_PATH = 'fixtures_setup.xcodeproj' let(:fixtures_project) do Slather::Project.open(FIXTURES_PROJECT_PATH) end describe "#build_directory" do it "should return the build_directory property, if it has been explicitly set" do build_directory_mock = double(String) fixtures_project.build_directory = build_directory_mock expect(fixtures_project.build_directory).to eq(build_directory_mock) end end describe "::yml" do before(:each) { Slather::Project.instance_variable_set("@yml", nil) } context ".slather.yml file exists" do before(:all) { File.open(".slather.yml", "w") { |f| f.write("two: 2") } } after(:all) { File.delete(".slather.yml") } it "should load and return .slather.yml, if it exists" do expect(Slather::Project.yml).to eq({"two" => 2}) end end context ".slather.yml file doesn't exist" do it "should return an empy hash" do expect(Slather::Project.yml).to eq({}) end end end describe "#coverage_files" do class SpecCoverageFile < Slather::CoverageFile end before(:each) do allow(Dir).to receive(:[]).and_call_original allow(Dir).to receive(:[]).with("#{fixtures_project.build_directory}/**/*.gcno").and_return(["/some/path/fixtures.gcno", "/some/path/peekaview.gcno", "/some/path/fixturesTests.gcno", "/some/path/peekaviewTests💣.gcno", "/some/path/NotInProject.gcno", "/some/path/NSRange.gcno"]) allow(fixtures_project).to receive(:coverage_file_class).and_return(SpecCoverageFile) end it "should return coverage file objects of type coverage_file_class for unignored project files" do fixtures_project.ignore_list = ["*fixturesTests*"] allow(fixtures_project).to receive(:dedupe) { |coverage_files| coverage_files } coverage_files = fixtures_project.coverage_files coverage_files.each { |cf| expect(cf.kind_of?(SpecCoverageFile)).to be_truthy } expect(coverage_files.map { |cf| cf.source_file_pathname.basename.to_s }).to eq(["fixtures.m", "peekaview.m"]) end it "should raise an exception if no unignored project coverage file files were found" do fixtures_project.ignore_list = ["*fixturesTests*", "*fixtures*"] expect {fixtures_project.coverage_files}.to raise_error(StandardError) end end describe "#profdata_coverage_files with large file lists" do class SpecXcode7CoverageFile < Slather::ProfdataCoverageFile end llvm_cov_export_output = %q( { "data":[ { "files":[ { "filename":"spec/fixtures/fixtures/Fixtures.swift" } ] }, { "files":[ { "filename":"spec/fixtures/fixtures/Fixtures.swift" } ] } ] }) profdata_llvm_cov_output = "#{FIXTURES_SWIFT_FILE_PATH}: | 0| | 1|import UIKit | 2| | 3|@UIApplicationMain | 4|class AppDelegate: UIResponder, UIApplicationDelegate { | 5| | 6| var window: UIWindow? | 7| 1| 8| func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 1| 9| return true 1| 10| } | 11| 0| 12| func applicationWillResignActive(application: UIApplication) { 0| 13| } 0| 14|}" before(:each) do allow(Dir).to receive(:[]).and_call_original allow(Dir).to receive(:[]).with("#{fixtures_project.build_directory}/**/Coverage.profdata").and_return(["/some/path/Coverage.profdata"]) allow(fixtures_project).to receive(:binary_file).and_return(["Fixtures"]) allow(fixtures_project).to receive(:llvm_cov_export_output).and_return(llvm_cov_export_output) allow(fixtures_project).to receive(:coverage_file_class).and_return(SpecXcode7CoverageFile) allow(fixtures_project).to receive(:ignore_list).and_return([]) end it "Should catch Errno::E2BIG and re-raise if the input is too large to split into multiple chunks" do allow(fixtures_project).to receive(:unsafe_profdata_llvm_cov_output).and_raise(Errno::E2BIG) expect { fixtures_project.send(:profdata_coverage_files) }.to raise_error(Errno::E2BIG, "Argument list too long. A path in your project is close to the E2BIG limit. https://github.com/SlatherOrg/slather/pull/414") end it "Should catch Errno::E2BIG and return Coverage.profdata file objects when the work can be split into two" do allow(fixtures_project).to receive(:unsafe_profdata_llvm_cov_output).once { # raise once and then insert the stub allow(fixtures_project).to receive(:profdata_llvm_cov_output).and_return(profdata_llvm_cov_output) raise Errno::E2BIG } profdata_coverage_files = fixtures_project.send(:profdata_coverage_files) profdata_coverage_files.each { |cf| expect(cf.kind_of?(SpecXcode7CoverageFile)).to be_truthy } expect(profdata_coverage_files.map { |cf| cf.source_file_pathname.basename.to_s }).to eq(["Fixtures.swift", "Fixtures.swift"]) end end describe "#profdata_coverage_files" do class SpecXcode7CoverageFile < Slather::ProfdataCoverageFile end llvm_cov_export_output = %q( { "data":[ { "files":[ { "filename":"spec/fixtures/fixtures/Fixtures.swift" } ] } ] }) profdata_llvm_cov_output = "#{FIXTURES_SWIFT_FILE_PATH}: | 0| | 1|import UIKit | 2| | 3|@UIApplicationMain | 4|class AppDelegate: UIResponder, UIApplicationDelegate { | 5| | 6| var window: UIWindow? | 7| 1| 8| func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 1| 9| return true 1| 10| } | 11| 0| 12| func applicationWillResignActive(application: UIApplication) { 0| 13| } 0| 14|}" before(:each) do allow(Dir).to receive(:[]).and_call_original allow(Dir).to receive(:[]).with("#{fixtures_project.build_directory}/**/Coverage.profdata").and_return(["/some/path/Coverage.profdata"]) allow(fixtures_project).to receive(:binary_file).and_return(["Fixtures"]) allow(fixtures_project).to receive(:llvm_cov_export_output).and_return(llvm_cov_export_output) allow(fixtures_project).to receive(:profdata_llvm_cov_output).and_return(profdata_llvm_cov_output) allow(fixtures_project).to receive(:coverage_file_class).and_return(SpecXcode7CoverageFile) allow(fixtures_project).to receive(:ignore_list).and_return([]) end it "should return Coverage.profdata file objects" do profdata_coverage_files = fixtures_project.send(:profdata_coverage_files) profdata_coverage_files.each { |cf| expect(cf.kind_of?(SpecXcode7CoverageFile)).to be_truthy } expect(profdata_coverage_files.map { |cf| cf.source_file_pathname.basename.to_s }).to eq(["Fixtures.swift"]) end it "should ignore files from the ignore list" do allow(fixtures_project).to receive(:ignore_list).and_return(["**/Fixtures.swift"]) profdata_coverage_files = fixtures_project.send(:profdata_coverage_files) expect(profdata_coverage_files.count).to eq(0) end end describe "#invalid_characters" do it "should correctly encode invalid characters" do allow(fixtures_project).to receive(:input_format).and_return("profdata") allow(fixtures_project).to receive(:ignore_list).and_return([]) allow(Dir).to receive(:[]).with("#{fixtures_project.build_directory}/**/Coverage.profdata").and_return(["/some/path/Coverage.profdata"]) allow(fixtures_project).to receive(:binary_file).and_return(["Fixtures"]) allow(fixtures_project).to receive(:unsafe_llvm_cov_export_output).and_return(" { \"data\":[ { \"files\":[ { \"filename\":\"sp\145c/fixtures/fixtures/fixtures.m\" } ] } ] }") allow(fixtures_project).to receive(:unsafe_profdata_llvm_cov_output).and_return("#{FIXTURES_SWIFT_FILE_PATH}: 1| 8| func application(application: \255, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 1| 9| return true 0| 14|}") fixtures_project.extend(Slather::CoverageService::HtmlOutput) profdata_coverage_files = fixtures_project.send(:profdata_coverage_files) expect(profdata_coverage_files.count).to eq(1) end end describe "#binary_file" do let(:build_directory) do TEMP_DERIVED_DATA_PATH end before(:each) do allow(Dir).to receive(:[]).and_call_original allow(fixtures_project).to receive(:build_directory).and_return(build_directory) allow(fixtures_project).to receive(:input_format).and_return("profdata") allow(fixtures_project).to receive(:scheme).and_return("fixtures") allow(Dir).to receive(:[]).with("#{build_directory}/**/CodeCoverage/FixtureScheme").and_return(["#{build_directory}/Build/Intermediates/CodeCoverage/FixtureScheme"]) allow(Dir).to receive(:[]).with("#{build_directory}/Build/Intermediates/CodeCoverage/FixtureScheme/**/*.xctest").and_return(["#{build_directory}/Build/Intermediates/CodeCoverage/FixtureScheme/FixtureAppTests.xctest"]) end it "should use binary_file" do fixtures_project.binary_file = ["/path/to/binary"] fixtures_project.send(:configure_binary_file) binary_file_location = fixtures_project.send(:binary_file) expect(binary_file_location.count).to eq(1) expect(binary_file_location.first).to eq("/path/to/binary") end it "should find the product path provided a scheme" do allow(fixtures_project).to receive(:scheme).and_return("fixtures") fixtures_project.send(:configure_binary_file) binary_file_location = fixtures_project.send(:binary_file) expect(binary_file_location.count).to eq(2) expect(binary_file_location.first).to end_with("Debug/FixtureFramework.framework/Versions/A/FixtureFramework") expect(binary_file_location.last).to end_with("Debug/fixturesTests.xctest/Contents/MacOS/fixturesTests") end it "should find the product path provided a workspace and scheme" do allow(fixtures_project).to receive(:workspace).and_return("fixtures.xcworkspace") allow(fixtures_project).to receive(:scheme).and_return("fixtures") fixtures_project.send(:configure_binary_file) binary_file_location = fixtures_project.send(:binary_file) expect(binary_file_location.count).to eq(2) expect(binary_file_location.first).to end_with("Debug/FixtureFramework.framework/Versions/A/FixtureFramework") expect(binary_file_location.last).to end_with("Debug/fixturesTests.xctest/Contents/MacOS/fixturesTests") end it "should find the product path for a scheme with no buildable products" do allow(fixtures_project).to receive(:scheme).and_return("fixturesTests") allow(fixtures_project).to receive(:arch).and_return("x86_64") fixtures_project.send(:configure_binary_file) binary_file_location = fixtures_project.send(:binary_file) expect(binary_file_location.count).to eq(1) expect(binary_file_location.first).to end_with("Debug/fixturesTests.xctest/Contents/MacOS/fixturesTests") end it "should not find a product path for an invalid architecture" do allow(fixtures_project).to receive(:scheme).and_return("fixturesTests") allow(fixtures_project).to receive(:arch).and_return("arm64") expect { fixtures_project.send(:configure_binary_file) }.to raise_error.with_message(/No product binary found in (.+)./) end it "should find multiple unique paths for a scheme with serveral buildable/testable products" do allow(fixtures_project).to receive(:scheme).and_return("aggregateFixturesTests") fixtures_project.send(:configure_binary_file) binary_file_location = fixtures_project.send(:binary_file) expect(binary_file_location).to contain_exactly( end_with("Debug/fixturesTests.xctest/Contents/MacOS/fixturesTests"), end_with("Debug/fixturesTestsSecond.xctest/Contents/MacOS/fixturesTestsSecond"), ) end let(:fixture_yaml) do yaml_text = <<-EOF binary_file: "/FixtureScheme/From/Yaml/Contents/MacOS/FixturesFromYaml" EOF yaml = YAML.load(yaml_text) end it "should configure the binary_file from yml" do allow(Slather::Project).to receive(:yml).and_return(fixture_yaml) fixtures_project.send(:configure_binary_file) binary_file_location = fixtures_project.send(:binary_file) expect(binary_file_location).to eq(["/FixtureScheme/From/Yaml/Contents/MacOS/FixturesFromYaml"]) end let(:other_fixture_yaml) do yaml_text = <<-EOF binary_basename: "fixtures" EOF yaml = YAML.load(yaml_text) end it "should configure the binary_basename from yml" do allow(Slather::Project).to receive(:yml).and_return(other_fixture_yaml) allow(Dir).to receive(:[]).with("#{build_directory}/Build/Intermediates/CodeCoverage/Products/Debug/fixtureTests.xctest").and_return(["fixtureTests.xctest"]) fixtures_project.send(:configure_binary_file) binary_file_location = fixtures_project.send(:binary_file) expect(binary_file_location.count).to eq(1) expect(binary_file_location.first).to end_with("/fixturesTests.xctest/Contents/MacOS/fixturesTests") end end describe "#dedupe" do it "should return a deduplicated list of coverage files, favoring the file with higher coverage" do coverage_file_1 = double(Slather::CoverageFile) allow(coverage_file_1).to receive(:source_file_pathname).and_return("some/path/class1.m") allow(coverage_file_1).to receive(:percentage_lines_tested).and_return(100) coverage_file_2 = double(Slather::CoverageFile) allow(coverage_file_2).to receive(:source_file_pathname).and_return("some/path/class2.m") allow(coverage_file_2).to receive(:percentage_lines_tested).and_return(100) coverage_file_2b = double(Slather::CoverageFile) allow(coverage_file_2b).to receive(:source_file_pathname).and_return("some/path/class2.m") allow(coverage_file_2b).to receive(:percentage_lines_tested).and_return(0) coverage_files = [coverage_file_1, coverage_file_2, coverage_file_2b] deduped_coverage_files = fixtures_project.send(:dedupe, coverage_files) expect(deduped_coverage_files.size).to eq(2) expect(deduped_coverage_files).to include(coverage_file_1) expect(deduped_coverage_files).to include(coverage_file_2) end end describe "#configure" do it "should configure all properties from the yml" do unstubbed_project = Slather::Project.open(FIXTURES_PROJECT_PATH) expect(unstubbed_project).to receive(:configure_build_directory) expect(unstubbed_project).to receive(:configure_source_directory) expect(unstubbed_project).to receive(:configure_ignore_list) expect(unstubbed_project).to receive(:configure_ci_service) expect(unstubbed_project).to receive(:configure_coverage_service) expect(unstubbed_project).to receive(:configure_input_format) expect(unstubbed_project).to receive(:configure_scheme) expect(unstubbed_project).to receive(:configure_configuration) expect(unstubbed_project).to receive(:configure_workspace) unstubbed_project.configure end end describe "#configure_ignore_list" do it "should set the ignore_list if it has been provided in the yml and has not already been set" do allow(Slather::Project).to receive(:yml).and_return({"ignore" => ["test", "ing"] }) fixtures_project.configure_ignore_list expect(fixtures_project.ignore_list).to eq(["test", "ing"]) end it "should force the ignore_list into an array" do allow(Slather::Project).to receive(:yml).and_return({"ignore" => "test" }) fixtures_project.configure_ignore_list expect(fixtures_project.ignore_list).to eq(["test"]) end it "should not set the ignore_list if it has already been set" do allow(Slather::Project).to receive(:yml).and_return({"ignore" => ["test", "ing"] }) fixtures_project.ignore_list = ["already", "set"] fixtures_project.configure_ignore_list expect(fixtures_project.ignore_list).to eq(["already", "set"]) end it "should default the ignore_list to an empty array if nothing is provided in the yml" do allow(Slather::Project).to receive(:yml).and_return({}) fixtures_project.configure_ignore_list expect(fixtures_project.ignore_list).to eq([]) end end describe "#configure_build_directory" do it "should set the build_directory if it has been provided in the yml and has not already been set" do allow(Slather::Project).to receive(:yml).and_return({"build_directory" => "/some/path"}) fixtures_project.configure_build_directory expect(fixtures_project.build_directory).to eq("/some/path") end it "should not set the build_directory if it has already been set" do allow(Slather::Project).to receive(:yml).and_return({"build_directory" => "/some/path"}) fixtures_project.build_directory = "/already/set" fixtures_project.configure_build_directory expect(fixtures_project.build_directory).to eq("/already/set") end it "should default the build_directory to derived data if nothing is provided in the yml" do allow(Slather::Project).to receive(:yml).and_return({}) fixtures_project.configure_build_directory expect(fixtures_project.build_directory).to eq(fixtures_project.send(:derived_data_path)) end end describe "#configure_source_directory" do it "should set the source_directory if it has been provided in the yml and has not already been set" do allow(Slather::Project).to receive(:yml).and_return({"source_directory" => "/some/path"}) fixtures_project.configure_source_directory expect(fixtures_project.source_directory).to eq("/some/path") end it "should not set the source_directory if it has already been set" do allow(Slather::Project).to receive(:yml).and_return({"source_directory" => "/some/path"}) fixtures_project.source_directory = "/already/set" fixtures_project.configure_source_directory expect(fixtures_project.source_directory).to eq("/already/set") end end describe "#configure_output_directory" do it "should set the output_directory if it has been provided in the yml and has not already been set" do allow(Slather::Project).to receive(:yml).and_return({"output_directory" => "/some/path"}) fixtures_project.configure_output_directory expect(fixtures_project.output_directory).to eq("/some/path") end it "should not set the output_directory if it has already been set" do allow(Slather::Project).to receive(:yml).and_return({"output_directory" => "/some/path"}) fixtures_project.output_directory = "/already/set" fixtures_project.configure_output_directory expect(fixtures_project.output_directory).to eq("/already/set") end end describe "#configure_configuration" do it "should set the configuration if it has been provided in the yml and has not already been set" do allow(Slather::Project).to receive(:yml).and_return({"configuration" => "Release"}) fixtures_project.configure_configuration expect(fixtures_project.configuration).to eq("Release") end it "should not set the configuration if it has already been set" do allow(Slather::Project).to receive(:yml).and_return({"configuration" => "Release"}) fixtures_project.configuration = "Debug" fixtures_project.configure_configuration expect(fixtures_project.configuration).to eq("Debug") end end describe "#configure_workspace" do it "should set the workspace if it has been provided in the yml and has not already been set" do allow(Slather::Project).to receive(:yml).and_return({"workspace" => "fixtures.xcworkspace"}) fixtures_project.configure_workspace expect(fixtures_project.workspace).to eq("fixtures.xcworkspace") end it "should not set the workspace if it has already been set" do allow(Slather::Project).to receive(:yml).and_return({"workspace" => "fixtures.xcworkspace"}) fixtures_project.workspace = "MyWorkspace.xcworkspace" fixtures_project.configure_workspace expect(fixtures_project.workspace).to eq("MyWorkspace.xcworkspace") end end describe "#configure_ci_service" do it "should set the ci_service if it has been provided in the yml and has not already been set" do allow(Slather::Project).to receive(:yml).and_return({"ci_service" => "some_service"}) fixtures_project.configure_ci_service expect(fixtures_project.ci_service).to eq(:some_service) end it "should not set the ci_service if it has already been set" do allow(Slather::Project).to receive(:yml).and_return({"ci_service" => "some service"}) fixtures_project.ci_service = "already_set" fixtures_project.configure_ci_service expect(fixtures_project.ci_service).to eq(:already_set) end it "should default the ci_service to :travis_ci if nothing is provided in the yml" do allow(Slather::Project).to receive(:yml).and_return({}) fixtures_project.configure_ci_service expect(fixtures_project.ci_service).to eq(:travis_ci) end end describe "#ci_service=" do it "should set the ci_service as a symbol" do fixtures_project.ci_service = "foobar" expect(fixtures_project.ci_service).to eq(:foobar) end end describe "#configure_coverage_service" do it "should set the coverage_service if it has been provided by the yml" do allow(Slather::Project).to receive(:yml).and_return({"coverage_service" => "some_service"}) expect(fixtures_project).to receive(:coverage_service=).with("some_service") fixtures_project.configure_coverage_service end it "should default the coverage_service to :terminal if nothing is provided in the yml" do allow(Slather::Project).to receive(:yml).and_return({}) expect(fixtures_project).to receive(:coverage_service=).with(:terminal) fixtures_project.configure_coverage_service end it "should not set the coverage_service if it has already been set" do allow(Slather::Project).to receive(:yml).and_return({"coverage_service" => "some_service" }) allow(fixtures_project).to receive(:coverage_service).and_return("already set") expect(fixtures_project).to_not receive(:coverage_service=) fixtures_project.configure_coverage_service end end describe "#configure_coverage_access_token" do it "should set the coverage_access_token if it has been provided by the yml" do allow(Slather::Project).to receive(:yml).and_return({"coverage_access_token" => "abc123"}) expect(fixtures_project).to receive(:coverage_access_token=).with("abc123") fixtures_project.configure_coverage_access_token end it "should set the coverage_access_token if it is in the ENV" do stub_const('ENV', ENV.to_hash.merge('COVERAGE_ACCESS_TOKEN' => 'asdf456')) expect(fixtures_project).to receive(:coverage_access_token=).with("asdf456") fixtures_project.configure_coverage_access_token end end describe "#configure_input_format" do it "should set the input_format if it has been provided by the yml" do allow(Slather::Project).to receive(:yml).and_return({"input_format" => "gcov"}) fixtures_project.configure_input_format expect(fixtures_project.input_format).to eq("gcov") end it "should default the input_format to auto if nothing is provided in the yml" do allow(Slather::Project).to receive(:yml).and_return({}) expect(fixtures_project).to receive(:input_format=).with("auto") fixtures_project.configure_input_format end it "should not set the input_format if it has already been set" do allow(Slather::Project).to receive(:yml).and_return({"input_format" => "some_format" }) fixtures_project.input_format = "gcov" expect(fixtures_project).to_not receive(:input_format=) fixtures_project.configure_input_format end end describe "#coverage_service=" do it "should extend Slather::CoverageService::Coveralls and set coverage_service = :coveralls if given coveralls" do expect(fixtures_project).to receive(:extend).with(Slather::CoverageService::Coveralls) fixtures_project.coverage_service = "coveralls" expect(fixtures_project.coverage_service).to eq(:coveralls) end it "should extend Slather::CoverageService::SimpleOutput and set coverage_service = :terminal if given terminal" do expect(fixtures_project).to receive(:extend).with(Slather::CoverageService::SimpleOutput) fixtures_project.coverage_service = "terminal" expect(fixtures_project.coverage_service).to eq(:terminal) end it "should raise an exception if it does not recognize the coverage service" do expect { fixtures_project.coverage_service = "xcode bots, lol" }.to raise_error(StandardError) end end describe "#slather_setup_for_coverage" do let(:fixtures_project_setup) do FileUtils.cp_r "#{FIXTURES_PROJECT_PATH}/", "#{FIXTURES_PROJECT_SETUP_PATH}/" allow_any_instance_of(Slather::Project).to receive(:configure) Slather::Project.open(FIXTURES_PROJECT_SETUP_PATH) end after(:each) do FileUtils.rm_rf(FIXTURES_PROJECT_SETUP_PATH) end it "should enable the correct flags to generate test coverage on all of the build_configurations build settings" do fixtures_project_setup.slather_setup_for_coverage fixtures_project_setup.build_configurations.each do |build_configuration| expect(build_configuration.build_settings["GCC_INSTRUMENT_PROGRAM_FLOW_ARCS"]).to eq("YES") expect(build_configuration.build_settings["GCC_GENERATE_TEST_COVERAGE_FILES"]).to eq("YES") end end it "should apply Xcode7 enableCodeCoverage setting" do fixtures_project_setup.slather_setup_for_coverage schemes_path = Xcodeproj::XCScheme.shared_data_dir(fixtures_project_setup.path) Xcodeproj::Project.schemes(fixtures_project_setup.path).each do |scheme_name| xcscheme_path = "#{schemes_path + scheme_name}.xcscheme" xcscheme = Xcodeproj::XCScheme.new(xcscheme_path) expect(xcscheme.test_action.xml_element.attributes['codeCoverageEnabled']).to eq("YES") end end it "should fail for unknown coverage type" do expect { fixtures_project_setup.slather_setup_for_coverage "this should fail" }.to raise_error(StandardError) end end describe "#verbose_mode" do let(:fixtures_project) do proj = Slather::Project.open(FIXTURES_PROJECT_PATH) proj.build_directory = TEMP_DERIVED_DATA_PATH proj.input_format = "profdata" proj.verbose_mode = true proj.configure proj end it "should print out environment info when in verbose_mode" do project_root = Pathname("./").realpath [/\nProcessing coverage file: #{project_root}\/spec\/DerivedData\/libfixtures\/Build\/ProfileData\/[A-Z0-9-]+\/Coverage.profdata/, "Against binary files:", "\t#{project_root}/spec/DerivedData/libfixtures/Build/Products/Debug/fixturesTests.xctest/Contents/MacOS/fixturesTests", "\n" ].each do |line| expect(fixtures_project).to receive(:puts).with(line) end fixtures_project.send(:configure) end it "should print error when no binaries found" do allow(fixtures_project).to receive(:binary_file).and_return(nil) project_root = Pathname("./").realpath [/\nProcessing coverage file: #{project_root}\/spec\/DerivedData\/libfixtures\/Build\/ProfileData\/[A-Z0-9-]+\/Coverage.profdata/, "No binary files found.", "\n", ].each do |line| expect(fixtures_project).to receive(:puts).with(line) end fixtures_project.send(:configure) end end describe "#source_files" do let(:fixtures_project) do proj = Slather::Project.open(FIXTURES_PROJECT_PATH) proj.build_directory = TEMP_DERIVED_DATA_PATH proj.input_format = "profdata" proj.source_files = ["./**/fixtures{,Two}.m"] proj.binary_basename = ["fixturesTests", "libfixturesTwo"] proj.configure proj end it "should find relevant source files" do source_files = fixtures_project.find_source_files expect(source_files.count).to eq(2) expect(source_files.first.to_s).to include("fixturesTwo.m") expect(source_files.last.to_s).to include("fixtures.m") end it "should print out the coverage for each file, and then total coverage" do allow(fixtures_project).to receive(:llvm_cov_export_output).and_return(%q( { "data":[ { "files":[ { "filename":"spec/fixtures/fixtures/fixtures.m", "segments": [] }, { "filename":"spec/fixtures/fixturesTwo/fixturesTwo.m", "segments": [] } ] } ] } )) ["spec/fixtures/fixtures/fixtures.m: 3 of 6 lines (50.00%)", "spec/fixtures/fixturesTwo/fixturesTwo.m: 6 of 6 lines (100.00%)", "Tested 9/12 statements", "Test Coverage: 75.00%" ].each do |line| expect(fixtures_project).to receive(:puts).with(line) end fixtures_project.post end end def decimal_f *args fixtures_project.decimal_f *args end describe '#decimal_f' do it 'should preserve length 2 decimals for backwards compatibility' do expect(decimal_f('100.00')).to eq('100.00') expect(decimal_f('50.00')).to eq('50.00') end it 'should convert length >= 3 decimals to floats' do fixtures_project.decimals = 3 expect(decimal_f('100.000')).to eq('100.0') expect(decimal_f('50.00000')).to eq('50.0') end end describe '#find_binary_files' do let(:configuration) { 'Debug' } let(:project_root) { Pathname("./").realpath } let(:coverage_dir) { "#{project_root}/spec/DerivedData/DerivedData/Build/Intermediates/CodeCoverage" } let(:search_dir) { "#{coverage_dir}/../../Products/#{configuration}*/fixtures*" } let(:binary_file) { "#{coverage_dir}/Products/#{configuration}-iphonesimulator/fixtures.app/fixtures" } before do allow(fixtures_project).to receive(:scheme).and_return("fixtures") allow(fixtures_project).to receive(:workspace).and_return("fixtures.xcworkspace") allow(fixtures_project).to receive(:binary_basename).and_return(["fixtures"]) allow(fixtures_project).to receive(:profdata_coverage_dir).and_return(coverage_dir) allow(Dir).to receive(:[]).with(search_dir).and_return([binary_file]) end context 'load configuration from xcsheme' do it "search binary from 'Products/Debug*'" do expect(fixtures_project.find_binary_files).to eq([binary_file]) end end context 'load configuration from option' do let(:configuration) { 'Release' } it "search binary from 'Products/Release*'" do fixtures_project.configuration = configuration expect(fixtures_project.find_binary_files).to eq([binary_file]) end end end end ================================================ FILE: spec/spec_helper.rb ================================================ if ENV['SIMPLECOV'] require 'simplecov' SimpleCov.start elsif ENV['TRAVIS'] require 'coveralls' Coveralls.wear! end require 'slather' require 'pry' require 'json_spec' require 'equivalent-xml' FIXTURES_XML_PATH = File.join(File.dirname(__FILE__), 'fixtures/cobertura.xml') FIXTURES_SONARQUBE_XML_PATH = File.join(File.dirname(__FILE__), 'fixtures/sonarqube-generic-coverage.xml') FIXTURES_JSON_PATH = File.join(File.dirname(__FILE__), 'fixtures/report.json') FIXTURES_LLCOV_PATH = File.join(File.dirname(__FILE__), 'fixtures/report.llcov') FIXTURES_GUTTER_JSON_PATH = File.join(File.dirname(__FILE__), 'fixtures/gutter.json') FIXTURES_HTML_FOLDER_PATH = File.join(File.dirname(__FILE__), 'fixtures/fixtures_html') FIXTURES_PROJECT_PATH = File.join(File.dirname(__FILE__), 'fixtures/fixtures.xcodeproj') FIXTURES_WORKSPACE_PATH = File.join(File.dirname(__FILE__), 'fixtures/fixtures.xcworkspace') FIXTURES_SWIFT_FILE_PATH = File.join(File.dirname(__FILE__), 'fixtures/fixtures/Fixtures.swift') TEMP_DERIVED_DATA_PATH = File.join(File.dirname(__FILE__), 'DerivedData') TEMP_PROJECT_BUILD_PATH = File.join(TEMP_DERIVED_DATA_PATH, "libfixtures") TEMP_WORKSPACE_BUILD_PATH = File.join(TEMP_DERIVED_DATA_PATH, "libfixtures") TEMP_OBJC_GCNO_PATH = File.join(File.dirname(__FILE__), 'fixtures/ObjectiveC.gcno') TEMP_OBJC_GCDA_PATH = File.join(File.dirname(__FILE__), 'fixtures/ObjectiveC.gcda') module FixtureHelpers def self.delete_derived_data dir = Dir[TEMP_DERIVED_DATA_PATH].first if dir FileUtils.rm_rf(dir) end end def self.delete_temp_gcov_files if File.file?(TEMP_OBJC_GCNO_PATH) FileUtils.rm(TEMP_OBJC_GCNO_PATH) end if File.file?(TEMP_OBJC_GCDA_PATH) FileUtils.rm_f(TEMP_OBJC_GCDA_PATH) end end end RSpec.configure do |config| config.before(:suite) do FixtureHelpers.delete_derived_data FixtureHelpers.delete_temp_gcov_files `xcodebuild -project "#{FIXTURES_PROJECT_PATH}" -scheme fixtures -configuration Debug -derivedDataPath #{TEMP_PROJECT_BUILD_PATH} -enableCodeCoverage YES clean test` `xcodebuild -workspace "#{FIXTURES_WORKSPACE_PATH}" -scheme aggregateFixturesTests -configuration Debug -derivedDataPath #{TEMP_WORKSPACE_BUILD_PATH} -enableCodeCoverage YES clean test` end config.after(:suite) do FixtureHelpers.delete_derived_data FixtureHelpers.delete_temp_gcov_files end end JsonSpec.configure do exclude_keys "timestamp" end