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
129 changes: 99 additions & 30 deletions Sources/BuildTool/BuildTool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,9 @@ struct SpecCoverage: AsyncParsableCommand {
case couldNotFindTestTarget
case malformedSpecOneOfTag
case specUntestedTagMissingComment
case specOneOfIncorrectTotals(specPointID: String, coverageTagTotals: [Int], actualTotal: Int)
case specOneOfIncorrectIndices(specPointID: String, coverageTagIndices: [Int], expectedIndices: [Int])
case multipleConformanceTagTypes(specPointID: String, types: [String])
}

/**
Expand Down Expand Up @@ -324,6 +327,26 @@ struct SpecCoverage: AsyncParsableCommand {
case specOneOf(index: Int, total: Int, comment: String?)
case specPartial(comment: String?)
case specUntested(comment: String)

enum Case {
case spec
case specOneOf
case specPartial
case specUntested
}

var `case`: Case {
switch self {
case .spec:
.spec
case .specOneOf:
.specOneOf
case .specPartial:
.specPartial
case .specUntested:
.specUntested
}
}
Comment thread
lawrence-forooghian marked this conversation as resolved.
}

var type: `Type`
Expand Down Expand Up @@ -424,7 +447,7 @@ struct SpecCoverage: AsyncParsableCommand {
throw Error.conformanceToNonexistentSpecPoints(specPointIDs: invalidSpecPointIDs.sorted())
}

// 2. Find any conformance tags for non-testable spec points (see documentation of the `nonTestableSpecPointIDsWithConformanceTags` property) for motivation.
// 2. Find any conformance tags for non-testable spec points (see documentation of the `nonTestableSpecPointIDsWithConformanceTags` property for motivation).
let specPointsByID = Dictionary(grouping: specFile.specPoints, by: \.id)

var nonTestableSpecPointIDsWithConformanceTags: Set<String> = []
Expand All @@ -436,37 +459,11 @@ struct SpecCoverage: AsyncParsableCommand {
}
}

// 3. Determine the coverage of each testable spec point.
// 3. Validate the spec coverage tags, and determine the coverage of each testable spec point.
let testableSpecPoints = specFile.specPoints.filter(\.isTestable)
let specPointCoverages = testableSpecPoints.map { specPoint in
var coverageLevel: CoverageLevel?
var comments: [String] = []

let specPointCoverages = try testableSpecPoints.map { specPoint in
let conformanceTagsForSpecPoint = conformanceTagsBySpecPointID[specPoint.id, default: []]
// TODO: https://github.com/ably-labs/ably-chat-swift/issues/96 - check for contradictory tags, validate the specOneOf(m, n) tags
for conformanceTag in conformanceTagsForSpecPoint {
// We only make use of the comments that explain why something is untested or partially tested.
switch conformanceTag.type {
case .spec:
coverageLevel = .tested
case .specOneOf:
coverageLevel = .tested
case let .specPartial(comment: comment):
coverageLevel = .partiallyTested
if let comment {
comments.append(comment)
}
case let .specUntested(comment: comment):
coverageLevel = .implementedButDeliberatelyNotTested
comments.append(comment)
}
}

return SpecPointCoverage(
specPointID: specPoint.id,
coverageLevel: coverageLevel ?? .notTested,
comments: comments
)
return try generateCoverage(for: specPoint, conformanceTagsForSpecPoint: conformanceTagsForSpecPoint)
}

return .init(
Expand All @@ -479,6 +476,78 @@ struct SpecCoverage: AsyncParsableCommand {
nonTestableSpecPointIDsWithConformanceTags: nonTestableSpecPointIDsWithConformanceTags
)
}

/// Validates the spec coverage tags for this spec point, and determines its coverage.
private static func generateCoverage(for specPoint: SpecFile.SpecPoint, conformanceTagsForSpecPoint: [ConformanceTag]) throws -> SpecPointCoverage {
// Calculated data to be used in output
var coverageLevel: CoverageLevel?
var comments: [String] = []

// Bookkeeping data for validation of conformance tags
var specOneOfDatas: [(index: Int, total: Int)] = []
var conformanceTagTypeCases: Set<ConformanceTag.`Type`.Case> = []

for conformanceTag in conformanceTagsForSpecPoint {
// We only make use of the comments that explain why something is untested or partially tested.
switch conformanceTag.type {
case .spec:
coverageLevel = .tested
case let .specOneOf(index: index, total: total, _):
coverageLevel = .tested
specOneOfDatas.append((index: index, total: total))
case let .specPartial(comment: comment):
coverageLevel = .partiallyTested
if let comment {
comments.append(comment)
}
case let .specUntested(comment: comment):
coverageLevel = .implementedButDeliberatelyNotTested
comments.append(comment)
}

conformanceTagTypeCases.insert(conformanceTag.type.case)
}

// Before returning, we validate the conformance tags for this spec point:

// 1. Check we don't have more than one type of conformance tag for this spec point.
if conformanceTagTypeCases.count > 1 {
throw Error.multipleConformanceTagTypes(
specPointID: specPoint.id,
types: conformanceTagTypeCases.map { "\($0)" }
)
}

// 2. Validate the data attached to the @specOneOf(m/n) conformance tags.
if !specOneOfDatas.isEmpty {
// Do the totals stated in the tags match the number of tags?
let coverageTagTotals = specOneOfDatas.map(\.total)
if !(coverageTagTotals.allSatisfy { $0 == specOneOfDatas.count }) {
throw Error.specOneOfIncorrectTotals(
specPointID: specPoint.id,
coverageTagTotals: specOneOfDatas.map(\.total),
actualTotal: specOneOfDatas.count
)
}

// Are the indices as expected?
let coverageTagIndices = specOneOfDatas.map(\.index).sorted()
let expectedIndices = Array(1 ... specOneOfDatas.count)
if coverageTagIndices != expectedIndices {
throw Error.specOneOfIncorrectIndices(
specPointID: specPoint.id,
coverageTagIndices: coverageTagIndices,
expectedIndices: expectedIndices
)
}
}

return SpecPointCoverage(
specPointID: specPoint.id,
coverageLevel: coverageLevel ?? .notTested,
comments: comments
)
}
}

private struct CoverageReportViewModel {
Expand Down
97 changes: 0 additions & 97 deletions Tests/AblyChatTests/MessageTests.swift

This file was deleted.