Add shellcheck, SwiftFormat and SwiftLint by tylervick · Pull Request #410 · XCTestHTMLReport/XCTestHTMLReport · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .github/workflows/lint.yml
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,8 @@ xcuserdata/
!*.xcodeproj/xcshareddata/
!*.xcworkspace/contents.xcworkspacedata
**/xcshareddata/WorkspaceSettings.xcsettings

# Generated by `swift test` (CliTests writes a report into the fixture dir).
Tests/XCTestHTMLReportTests/Resources/index.html
Tests/XCTestHTMLReportTests/Resources/report.junit
Tests/XCTestHTMLReportTests/Resources/report.json
7 changes: 7 additions & 0 deletions .swiftformat
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# HTMLTemplates.swift is generated (it carries a DO NOT EDIT header) and is
# almost entirely multiline string literals whose contents are the emitted HTML.
# Reformatting it moves the closing """ delimiters, which changes how much
# leading whitespace Swift strips from every line — i.e. it changes the bytes of
# every generated report. Left alone deliberately.
--exclude Sources/XCTestHTMLReportCore/Classes/HTMLTemplates.swift

--swiftversion 5.5
--acronyms ID,URL,UUID
--allman false
Expand Down
43 changes: 43 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Starting configuration. The intent is a gate that is green today and can be
# tightened deliberately, rather than one that is switched on already failing
# and therefore ignored.

included:
- Sources
- Tests
- XCTestHTMLReportSampleApp

excluded:
- .build
# Generated (carries a DO NOT EDIT header) and almost entirely multiline
# string literals holding the emitted HTML. Also excluded from SwiftFormat.
- Sources/XCTestHTMLReportCore/Classes/HTMLTemplates.swift

disabled_rules:
# SwiftFormat owns layout; running both would mean two tools disagreeing
# about the same lines.
- trailing_comma
- trailing_whitespace
- opening_brace
- statement_position
# TODOs are tracked as GitHub issues in this repo, not as lint failures.
- todo

line_length:
# SwiftFormat wraps at 100, but it cannot wrap string literals or long
# identifiers, so the error threshold sits above that rather than failing
# on lines no formatter can fix.
warning: 120
error: 200
ignores_comments: true
ignores_urls: true

identifier_name:
# Short names are idiomatic in this codebase's closures and generics.
min_length:
warning: 2
error: 1
excluded:
- id
- to
- i
13 changes: 10 additions & 3 deletions Sources/XCTestHTMLReport/XCTestHtmlReport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,13 @@ struct SummaryOptions: ParsableArguments {

@Flag(name: .customShort("z"), help: ArgumentHelp("Downsize image screenshots"))
var downsizeImages = false

@Option(name: .long, help: ArgumentHelp("Downsize scale factor. A float between 0 and 1. No effect unless downsizeImages is enabled (-z)."))

@Option(
name: .long,
help: ArgumentHelp(
"Downsize scale factor. A float between 0 and 1. No effect unless downsizeImages is enabled (-z)."
)
)
var downsizeScaleFactor = 0.25

@Option(help: ArgumentHelp("Render attachments inline or as linked assets"))
Expand Down Expand Up @@ -180,7 +185,9 @@ struct XCTestHtmlReport: ParsableCommand {

summary.validate()
let faults = summary.faults
guard !faults.isEmpty else { return }
guard !faults.isEmpty else {
return
}

Logger.warning("Report is degraded: \(faults.count) fault(s)")
for fault in faults {
Expand Down
42 changes: 30 additions & 12 deletions Sources/XCTestHTMLReportCore/Classes/Helpers/ImageHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,48 @@ enum ResizeError: Error {
extension RenderingContent {
static let imageCompression: Float = 0.8

static func downsizeFrom(_ content: RenderingContent, downsizeScaleFactor: CGFloat) throws -> RenderingContent {
static func downsizeFrom(
_ content: RenderingContent,
downsizeScaleFactor: CGFloat
) throws -> RenderingContent {
switch content {
case let .data(data):
return .data(try RenderingContent.resize(content: data, downsizeScaleFactor: downsizeScaleFactor))
return try .data(RenderingContent.resize(
content: data,
downsizeScaleFactor: downsizeScaleFactor
))
case let .url(url):
return .url(try RenderingContent.resize(content: url, downsizeScaleFactor: downsizeScaleFactor))
return try .url(RenderingContent.resize(
content: url,
downsizeScaleFactor: downsizeScaleFactor
))
case .none:
throw ResizeError.contentNotImage
}
}

/// Performs an resize for the image data, scaling to 0.25 of the size while maintaining aspect ratio

/// Performs an resize for the image data, scaling to 0.25 of the size while maintaining aspect
/// ratio
/// - Parameter content: NSImageResizable-conforming object, typically Data
/// - Returns: A representation of the resized image
private static func resize<C: NSImageResizable>(content: C, downsizeScaleFactor: CGFloat) throws -> C {
private static func resize<C: NSImageResizable>(
content: C,
downsizeScaleFactor: CGFloat
) throws -> C {
let image = try content.asNSImage()
let originalSize = image.size
let newSize = CGSize(width: originalSize.width * downsizeScaleFactor, height: originalSize.height * downsizeScaleFactor)
let newSize = CGSize(
width: originalSize.width * downsizeScaleFactor,
height: originalSize.height * downsizeScaleFactor
)

let newImage = NSImage(size: newSize, flipped: false) { rect in
image.draw(in: rect,
from: CGRect(origin: .zero, size: originalSize),
operation: .copy,
fraction: 1)
image.draw(
in: rect,
from: CGRect(origin: .zero, size: originalSize),
operation: .copy,
fraction: 1
)
return true
}

Expand Down Expand Up @@ -145,7 +163,7 @@ extension CGSize: Comparable {
return CGSize(width: size.width * ratio, height: size.height * ratio)
}

// Compares by area
/// Compares by area
public static func < (lhs: CGSize, rhs: CGSize) -> Bool {
(lhs.width * lhs.height) < (rhs.width * rhs.height)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ func removeUnattachedFiles(runs: [Run]) -> Int {

var attachmentPathsLastItem: [String?] = []
for run in runs {
attachmentPathsLastItem = attachmentPathsLastItem + run.allAttachments
attachmentPathsLastItem += run.allAttachments
.map { $0.source?.lastPathComponent() }
if case let RenderingContent.url(url) = run.logContent {
attachmentPathsLastItem.append(url.lastPathComponent)
}
}

func shouldBeDeleted(fileURL: URL) -> Bool {
/// Do not delete directories
// Do not delete directories
var isDir: ObjCBool = false
if fileManager.fileExists(atPath: fileURL.path, isDirectory: &isDir), isDir.boolValue {
return false
Expand Down
5 changes: 4 additions & 1 deletion Sources/XCTestHTMLReportCore/Classes/Models/Attachment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,10 @@ struct Attachment: HTML {
.appendingPathComponent(url.relativeString)
), downsizeScaleFactor: downsizeScaleFactor)
} else {
content = try RenderingContent.downsizeFrom(content, downsizeScaleFactor: downsizeScaleFactor)
content = try RenderingContent.downsizeFrom(
content,
downsizeScaleFactor: downsizeScaleFactor
)
}
} catch {
Logger.error("Image resize failed with error: \(error.localizedDescription)")
Expand Down
22 changes: 13 additions & 9 deletions Sources/XCTestHTMLReportCore/Classes/Models/Iteration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,16 +91,20 @@ struct Iteration: Test {
}

extension Iteration {
var htmlPlaceholderValues: [String: String] { [
"UUID": uuid,
"TITLE": "Iteration \(repetitionPolicy?.iteration ?? 0)",
"DURATION": duration.formattedSeconds,
"ICON_CLASS": status.cssClass,
"SCREENSHOT_FLOW": testScreenshotFlow?.screenshots.accumulateHTMLAsString ?? "",
"ACTIVITIES": activities.accumulateHTMLAsString,
] }
var htmlPlaceholderValues: [String: String] {
[
"UUID": uuid,
"TITLE": "Iteration \(repetitionPolicy?.iteration ?? 0)",
"DURATION": duration.formattedSeconds,
"ICON_CLASS": status.cssClass,
"SCREENSHOT_FLOW": testScreenshotFlow?.screenshots.accumulateHTMLAsString ?? "",
"ACTIVITIES": activities.accumulateHTMLAsString,
]
}

var htmlTemplate: String { HTMLTemplates.iteration }
var htmlTemplate: String {
HTMLTemplates.iteration
}
}

extension Iteration: ContainingAttachment {
Expand Down
26 changes: 15 additions & 11 deletions Sources/XCTestHTMLReportCore/Classes/Models/JUnitReport.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// JUnit.swift
// JUnitReport.swift
// XCTestHTMLReport
//
// Created by Chris Ballinger on 4/25/18.
Expand All @@ -17,7 +17,7 @@
var failures: Int {
suites.map(\.failures).reduce(0) { $0 + $1 }
}

var skipped: Int {
suites.map(\.skipped).reduce(0) { $0 + $1 }
}
Expand All @@ -30,6 +30,7 @@
var failures: Int {
cases.filter { $0.state == .failed }.count
}

var skipped: Int {
cases.filter { $0.state == .skipped }.count
}
Expand All @@ -38,7 +39,7 @@
}

struct TestCase {
enum State {

Check warning on line 42 in Sources/XCTestHTMLReportCore/Classes/Models/JUnitReport.swift

View workflow job for this annotation

GitHub Actions / swift

Types should be nested at most 1 level deep (nesting)
case unknown
case failed
case passed
Expand All @@ -55,7 +56,7 @@
}

struct TestResult {
enum State {

Check warning on line 59 in Sources/XCTestHTMLReportCore/Classes/Models/JUnitReport.swift

View workflow job for this annotation

GitHub Actions / swift

Types should be nested at most 1 level deep (nesting)
case unknown
case failed
case systemOut
Expand All @@ -76,9 +77,9 @@
var xmlString: String {
var xml = "<?xml version='1.0' encoding='UTF-8'?>\n"
xml +=
"<testsuites name='\(name.stringByEscapingXMLChars)' tests='\(tests)' failures='\(failures)' skipped='\(skipped)'>\n"

Check warning on line 80 in Sources/XCTestHTMLReportCore/Classes/Models/JUnitReport.swift

View workflow job for this annotation

GitHub Actions / swift

Line should be 120 characters or less; currently it has 129 characters (line_length)

suites.forEach { suite in
for suite in suites {
xml += suite.xmlString
}

Expand All @@ -92,9 +93,9 @@
/// e.g. <testsuite name='AccessTests' tests='1' failures='0'>
var xmlString: String {
var xml =
" <testsuite name='\(name.stringByEscapingXMLChars)' tests='\(tests)' failures='\(failures)' skipped='\(skipped)'>\n"

Check warning on line 96 in Sources/XCTestHTMLReportCore/Classes/Models/JUnitReport.swift

View workflow job for this annotation

GitHub Actions / swift

Line should be 120 characters or less; currently it has 130 characters (line_length)

cases.forEach { testcase in
for testcase in cases {
xml += testcase.xmlString
}

Expand All @@ -110,10 +111,11 @@
var xmlString: String {
let timeString = String(format: "%.02f", time)
var xml =
" <testcase classname='\(classname.stringByEscapingXMLChars)' name='\(name.stringByEscapingXMLChars)' time='\(timeString)'"

Check warning on line 114 in Sources/XCTestHTMLReportCore/Classes/Models/JUnitReport.swift

View workflow job for this annotation

GitHub Actions / swift

Line should be 120 characters or less; currently it has 136 characters (line_length)

/// Skipped tests can have no TestResults (logs) so we need to check the status to add the skipped tag to the xml file
if self.state == .skipped {

// Skipped tests can have no TestResults (logs) so we need to check the status to add the
// skipped tag to the xml file
if state == .skipped {
xml += ">\n"
xml += " <skipped/>"
xml += "\n </testcase>\n"
Expand Down Expand Up @@ -184,14 +186,14 @@
case .unknown:
state = .unknown
}
// Activities can be nested in infinite levels so here everything should be flatted
// To replicate cascading we add some indent
/// Activities can be nested in infinite levels so here everything should be flatted
/// To replicate cascading we add some indent
func flatSubActivities(
of activity: Activity,
indent: Int,
isFailureFatal: Bool
) -> [JUnitReport.TestResult] {
let t = activity.subActivities

Check warning on line 196 in Sources/XCTestHTMLReportCore/Classes/Models/JUnitReport.swift

View workflow job for this annotation

GitHub Actions / swift

Variable name 't' should be between 2 and 40 characters long (identifier_name)
.flatMap {
flatSubActivities(of: $0, indent: indent + 1, isFailureFatal: isFailureFatal)
}
Expand All @@ -205,7 +207,8 @@
for (index, iteration) in testCase.iterations.enumerated() {
let isFailureFatal = {
let isLastIteration = index == testCase.iterations.indices.last
// In some edge cases, there may be failed assertions during the test, but the test succeeds.
// In some edge cases, there may be failed assertions during the test, but the
// test succeeds.
// To consider an assertion fatal, the test state must be failure
return isLastIteration && iteration.didFail
}()
Expand All @@ -214,7 +217,8 @@
.map { flatSubActivities(of: $0, indent: 0, isFailureFatal: isFailureFatal) }
.flatMap { $0 }

// In the edge case the test state failed but there were zero failed assertions in the test steps,
// In the edge case the test state failed but there were zero failed assertions in
// the test steps,
// add a final step that indicates the failed test state
if isFailureFatal, !results.contains(where: { $0.state == .failed }) {
results += [.init(title: "Test status: \(iteration.status)", state: .failed)]
Expand Down
14 changes: 7 additions & 7 deletions Sources/XCTestHTMLReportCore/Classes/Models/Run.swift
Loading
Loading