File: IssueManager.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (152 lines) | stat: -rw-r--r-- 6,226 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import Common
import Foundation
import TSCBasic

/// Keeps track of the detected failures and their status (expected/unexpected)
/// across multiple wrapper invocations
public struct IssueManager {
  let activeConfig: String
  let expectedIssuesFile: URL
  let resultsFile: URL
  let encoder: JSONEncoder
  let activeRequests: Set<RequestKind>

  /// - parameters:
  ///   - expectedFailuresFile: the URL of a JSON file describing the
  ///   expected failures
  ///   - resultsFile: the URL of the file to use to store failures across
  ///   multiple invocations
  init(activeConfig: String, expectedIssuesFile: URL, resultsFile: URL,
       activeRequests: Set<RequestKind>) {
    self.activeConfig = activeConfig
    self.expectedIssuesFile = expectedIssuesFile
    self.resultsFile = resultsFile
    self.activeRequests = activeRequests
    self.encoder = JSONEncoder()
    encoder.outputFormatting = .prettyPrinted
  }

  /// Updates this instance's backing resultsFile to account for the files
  /// processed by this invocation of the wrapper, and the issue it detected,
  /// if any.
  ///
  /// - parameters:
  ///   - for: the set of files processed
  ///   - issue: the detected issue, if any
  func update(for files: Set<String>, issue: StressTesterIssue?) throws -> ExpectedIssue? {
    return try localFileSystem.withLock(on: AbsolutePath(resultsFile.path), type: .exclusive) {
      let failureSpecs = try getIssueSpecifications(applicableTo: files)
      var state = try getCurrentState()

      var matchingSpec: ExpectedIssue? = nil
      let added = files.subtracting(state.processedFiles)
      state.unmatchedExpectedIssues.append(contentsOf: failureSpecs.filter { spec in
        added.contains {spec.isApplicable(toPath: $0)}
      })
      state.processedFiles.formUnion(files)
      if let issue = issue {
        if let match = failureSpecs.first(where: {$0.matches(issue)}) {
          state.expectedIssues[match.issueUrl, default: []].append(issue)
          state.expectedIssueMessages[match.issueUrl, default: []].append(String(describing: issue))
          state.unmatchedExpectedIssues.removeAll(where: {$0 == match})
          matchingSpec = match
        } else if !issue.isSoftError {
          let xfail = ExpectedIssue(matching: issue, issueUrl: "<issue url>",
                                    config: activeConfig)
          let json = try encoder.encode(xfail)
          state.issues.append(issue)
          state.issueMessages.append("""
            \(String(describing: issue))
            Add the following entry to the expected failures JSON file to mark it as expected:
            \(String(data: json, encoding: .utf8)!)"
            """)
        }
      }

      let data = try encoder.encode(state)
      FileManager.default.createFile(atPath: resultsFile.path, contents: data)
      return matchingSpec
    }
  }

  private func getIssueSpecifications(applicableTo files: Set<String>) throws -> [ExpectedIssue] {
    let data = try Data(contentsOf: expectedIssuesFile)
    return try JSONDecoder().decode([ExpectedIssue].self, from: data)
      .filter { spec in
        return spec.applicableConfigs.contains(activeConfig) &&
          files.contains { spec.isApplicable(toPath: $0) } &&
          activeRequests.contains(where: { spec.issueDetail.enabledFor(request: $0) })
    }
  }

  private func getCurrentState() throws -> IssueManagerState {
    guard FileManager.default.fileExists(atPath: resultsFile.path) else {
      return IssueManagerState()
    }
    let data = try Data(contentsOf: resultsFile)
    return try JSONDecoder().decode(IssueManagerState.self, from: data)
  }
}

/// Holds the state of the IssueManager that will be serialized across
/// invocations
fileprivate struct IssueManagerState: Codable {
  var expectedIssues = Dictionary<String, [StressTesterIssue]>()
  var expectedIssueMessages = Dictionary<String, [String]>()
  var issues = [StressTesterIssue]()
  var issueMessages = [String]()

  var processedFiles = Set<String>()
  var unmatchedExpectedIssues = [ExpectedIssue]()
}

extension StressTesterIssue: Codable {
  private enum CodingKeys: String, CodingKey {
    case kind, sourceKitError, status, file, arguments
  }
  private enum BaseMessage: String, Codable {
    case failed, errored
  }

  public init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    switch try container.decode(BaseMessage.self, forKey: .kind) {
    case .failed:
      let sourceKitError = try container.decode(SourceKitError.self, forKey: .sourceKitError)
      let arguments = try container.decode(String.self, forKey: .arguments)
      self = .failed(sourceKitError: sourceKitError, arguments: arguments)
    case .errored:
      let status = try container.decode(Int32.self, forKey: .status)
      let file = try container.decode(String.self, forKey: .file)
      let arguments = try container.decode(String.self, forKey: .arguments)
      self = .errored(status: status, file: file, arguments: arguments)
    }
  }

  public func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    switch self {
    case .failed(let sourceKitError, let arguments):
      try container.encode(BaseMessage.failed, forKey: .kind)
      try container.encode(sourceKitError, forKey: .sourceKitError)
      try container.encode(arguments, forKey: .arguments)
    case .errored(let status, let file, let arguments):
      try container.encode(BaseMessage.errored, forKey: .kind)
      try container.encode(status, forKey: .status)
      try container.encode(file, forKey: .file)
      try container.encode(arguments, forKey: .arguments)
    }
  }
}