001/* 002 * Copyright (C) 2022 - 2023, the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package io.github.ascopes.jct.assertions; 017 018import static io.github.ascopes.jct.utils.IterableUtils.combineOneOrMore; 019import static io.github.ascopes.jct.utils.IterableUtils.requireNonNullValues; 020import static java.util.Objects.requireNonNull; 021import static java.util.function.Predicate.not; 022 023import io.github.ascopes.jct.diagnostics.TraceDiagnostic; 024import io.github.ascopes.jct.repr.TraceDiagnosticListRepresentation; 025import io.github.ascopes.jct.utils.StringUtils; 026import java.util.ArrayList; 027import java.util.LinkedHashSet; 028import java.util.List; 029import java.util.Locale; 030import java.util.function.Predicate; 031import java.util.stream.Collectors; 032import java.util.stream.StreamSupport; 033import javax.tools.Diagnostic.Kind; 034import javax.tools.JavaFileObject; 035import org.apiguardian.api.API; 036import org.apiguardian.api.API.Status; 037import org.assertj.core.api.AbstractListAssert; 038 039/** 040 * Assertions for a list of diagnostics. 041 * 042 * @author Ashley Scopes 043 * @since 0.0.1 044 */ 045@API(since = "0.0.1", status = Status.STABLE) 046public final class TraceDiagnosticListAssert 047 extends AbstractListAssert<TraceDiagnosticListAssert, List<? extends TraceDiagnostic<? extends JavaFileObject>>, TraceDiagnostic<? extends JavaFileObject>, TraceDiagnosticAssert> { 048 049 /** 050 * Initialize this assertion. 051 * 052 * @param traceDiagnostics the diagnostics to perform assertions on. 053 */ 054 public TraceDiagnosticListAssert( 055 List<? extends TraceDiagnostic<? extends JavaFileObject>> traceDiagnostics 056 ) { 057 super(traceDiagnostics, TraceDiagnosticListAssert.class); 058 info.useRepresentation(TraceDiagnosticListRepresentation.getInstance()); 059 } 060 061 /** 062 * Get a {@link TraceDiagnosticListAssert} across all diagnostics that have the {@link Kind#ERROR} 063 * kind. 064 * 065 * @return the assertion object for {@link Kind#ERROR} diagnostics. 066 * @throws AssertionError if the list is null. 067 */ 068 public TraceDiagnosticListAssert errors() { 069 return filteringByKinds(DiagnosticKindAssert.ERROR_DIAGNOSTIC_KINDS); 070 } 071 072 /** 073 * Get a {@link TraceDiagnosticListAssert} across all diagnostics that have the 074 * {@link Kind#WARNING} or {@link Kind#MANDATORY_WARNING} kind. 075 * 076 * @return the assertion object for {@link Kind#WARNING} and {@link Kind#MANDATORY_WARNING} 077 * diagnostics. 078 * @throws AssertionError if the list is null. 079 */ 080 public TraceDiagnosticListAssert warnings() { 081 return filteringByKinds(DiagnosticKindAssert.WARNING_DIAGNOSTIC_KINDS); 082 } 083 084 /** 085 * Get a {@link TraceDiagnosticListAssert} across all diagnostics that have the 086 * {@link Kind#WARNING} kind. 087 * 088 * @return the assertion object for {@link Kind#WARNING} diagnostics. 089 * @throws AssertionError if the list is null. 090 */ 091 public TraceDiagnosticListAssert customWarnings() { 092 return filteringByKinds(Kind.WARNING); 093 } 094 095 /** 096 * Get a {@link TraceDiagnosticListAssert} across all diagnostics that have the 097 * {@link Kind#MANDATORY_WARNING} kind. 098 * 099 * @return the assertion object for {@link Kind#MANDATORY_WARNING} diagnostics. 100 * @throws AssertionError if the list is null. 101 */ 102 public TraceDiagnosticListAssert mandatoryWarnings() { 103 return filteringByKinds(Kind.MANDATORY_WARNING); 104 } 105 106 /** 107 * Get a {@link TraceDiagnosticListAssert} across all diagnostics that have the {@link Kind#NOTE} 108 * kind. 109 * 110 * @return the assertion object for {@link Kind#NOTE} diagnostics. 111 * @throws AssertionError if the list is null. 112 */ 113 public TraceDiagnosticListAssert notes() { 114 return filteringByKinds(Kind.NOTE); 115 } 116 117 /** 118 * Get a {@link TraceDiagnosticListAssert} across all diagnostics that have the {@link Kind#OTHER} 119 * kind. 120 * 121 * @return the assertion object for {@link Kind#OTHER} diagnostics. 122 * @throws AssertionError if the list is null. 123 */ 124 public TraceDiagnosticListAssert others() { 125 return filteringByKinds(Kind.OTHER); 126 } 127 128 /** 129 * Get a {@link TraceDiagnosticListAssert} that contains diagnostics corresponding to any of the 130 * given {@link Kind kinds}. 131 * 132 * @param kind the first kind to match. 133 * @param moreKinds additional kinds to match. 134 * @return the assertion object for the filtered diagnostics. 135 * @throws AssertionError if this list is null. 136 * @throws NullPointerException if any of the kinds are null. 137 */ 138 public TraceDiagnosticListAssert filteringByKinds(Kind kind, Kind... moreKinds) { 139 requireNonNull(kind, "kind must not be null"); 140 requireNonNullValues(moreKinds, "moreKinds"); 141 return filteringByKinds(combineOneOrMore(kind, moreKinds)); 142 } 143 144 145 /** 146 * Get a {@link TraceDiagnosticListAssert} that contains diagnostics corresponding to any of the 147 * given {@link Kind kinds}. 148 * 149 * @param kinds the kinds to match. 150 * @return the assertion object for the filtered diagnostics. 151 * @throws AssertionError if this list is null. 152 * @throws NullPointerException if any of the kinds are null. 153 */ 154 public TraceDiagnosticListAssert filteringByKinds(Iterable<Kind> kinds) { 155 requireNonNullValues(kinds, "kinds"); 156 return filteringBy(kind(kinds)); 157 } 158 159 /** 160 * Get a {@link TraceDiagnosticListAssert} that contains diagnostics corresponding to none of the 161 * given {@link Kind kinds}. 162 * 163 * @param kind the first kind to ensure are not matched. 164 * @param moreKinds additional kinds to ensure are not matched. 165 * @return the assertion object for the filtered diagnostics. 166 * @throws AssertionError if this list is null. 167 * @throws NullPointerException if any of the kinds are null. 168 */ 169 public TraceDiagnosticListAssert excludingKinds(Kind kind, Kind... moreKinds) { 170 requireNonNull(kind, "kind must not be null"); 171 requireNonNullValues(moreKinds, "moreKinds"); 172 return excludingKinds(combineOneOrMore(kind, moreKinds)); 173 } 174 175 /** 176 * Get a {@link TraceDiagnosticListAssert} that contains diagnostics corresponding to none of the 177 * given {@link Kind kinds}. 178 * 179 * @param kinds the kinds to filter out. 180 * @return the assertion object for the filtered diagnostics. 181 * @throws AssertionError if this list is null. 182 * @throws NullPointerException if any of the kinds are null. 183 */ 184 public TraceDiagnosticListAssert excludingKinds(Iterable<Kind> kinds) { 185 requireNonNullValues(kinds, "kinds"); 186 return filteringBy(not(kind(kinds))); 187 } 188 189 /** 190 * Assert that this list has no {@link Kind#ERROR} diagnostics. 191 * 192 * @return this assertion object for further call chaining. 193 * @throws AssertionError if this list is null. 194 */ 195 public TraceDiagnosticListAssert hasNoErrors() { 196 return hasNoDiagnosticsOfKinds(DiagnosticKindAssert.ERROR_DIAGNOSTIC_KINDS); 197 } 198 199 /** 200 * Assert that this list has no {@link Kind#ERROR}, {@link Kind#WARNING}, or 201 * {@link Kind#MANDATORY_WARNING} diagnostics. 202 * 203 * @return this assertion object for further call chaining. 204 * @throws AssertionError if this list is null. 205 */ 206 public TraceDiagnosticListAssert hasNoErrorsOrWarnings() { 207 return hasNoDiagnosticsOfKinds(DiagnosticKindAssert.WARNING_AND_ERROR_DIAGNOSTIC_KINDS); 208 } 209 210 /** 211 * Assert that this list has no {@link Kind#WARNING} or {@link Kind#MANDATORY_WARNING} 212 * diagnostics. 213 * 214 * @return this assertion object for further call chaining. 215 * @throws AssertionError if this list is null. 216 */ 217 public TraceDiagnosticListAssert hasNoWarnings() { 218 return hasNoDiagnosticsOfKinds(DiagnosticKindAssert.WARNING_DIAGNOSTIC_KINDS); 219 } 220 221 /** 222 * Assert that this list has no {@link Kind#WARNING} diagnostics. 223 * 224 * @return this assertion object for further call chaining. 225 * @throws AssertionError if this list is null. 226 */ 227 public TraceDiagnosticListAssert hasNoCustomWarnings() { 228 return hasNoDiagnosticsOfKinds(Kind.WARNING); 229 } 230 231 /** 232 * Assert that this list has no {@link Kind#MANDATORY_WARNING} diagnostics. 233 * 234 * @return this assertion object for further call chaining. 235 * @throws AssertionError if this list is null. 236 */ 237 public TraceDiagnosticListAssert hasNoMandatoryWarnings() { 238 return hasNoDiagnosticsOfKinds(Kind.MANDATORY_WARNING); 239 } 240 241 /** 242 * Assert that this list has no {@link Kind#NOTE} diagnostics. 243 * 244 * @return this assertion object for further call chaining. 245 * @throws AssertionError if this list is null. 246 */ 247 public TraceDiagnosticListAssert hasNoNotes() { 248 return hasNoDiagnosticsOfKinds(Kind.NOTE); 249 } 250 251 /** 252 * Assert that this list has no {@link Kind#OTHER} diagnostics. 253 * 254 * @return this assertion object for further call chaining. 255 * @throws AssertionError if this list is null. 256 */ 257 public TraceDiagnosticListAssert hasNoOtherDiagnostics() { 258 return hasNoDiagnosticsOfKinds(Kind.OTHER); 259 } 260 261 /** 262 * Assert that this list has no diagnostics matching any of the given kinds. 263 * 264 * @param kind the first kind to check for. 265 * @param moreKinds any additional kinds to check for. 266 * @return this assertion object for further call chaining. 267 * @throws AssertionError if the diagnostic list is null. 268 * @throws NullPointerException if the kind or more kinds are null. 269 */ 270 public TraceDiagnosticListAssert hasNoDiagnosticsOfKinds(Kind kind, Kind... moreKinds) { 271 requireNonNull(kind, "kind must not be null"); 272 requireNonNullValues(moreKinds, "moreKinds"); 273 return hasNoDiagnosticsOfKinds(combineOneOrMore(kind, moreKinds)); 274 } 275 276 /** 277 * Assert that this list has no diagnostics matching any of the given kinds. 278 * 279 * @param kinds the kinds to check for. 280 * @return this assertion object for further call chaining. 281 * @throws AssertionError if the diagnostic list is null. 282 * @throws NullPointerException if any of the kinds are null. 283 */ 284 public TraceDiagnosticListAssert hasNoDiagnosticsOfKinds(Iterable<Kind> kinds) { 285 requireNonNullValues(kinds, "kinds"); 286 287 var actualDiagnostics = actual 288 .stream() 289 .filter(kind(kinds)) 290 .collect(Collectors.toList()); 291 292 if (!actualDiagnostics.isEmpty()) { 293 var allKindsString = StreamSupport.stream(kinds.spliterator(), false) 294 .map(next -> next.name().toLowerCase(Locale.ROOT).replace('_', ' ')) 295 .sorted() 296 .collect(Collectors.collectingAndThen( 297 Collectors.toUnmodifiableList(), 298 names -> StringUtils.toWordedList(names, ", ", ", or ") 299 )); 300 301 failWithActualExpectedAndMessage( 302 actualDiagnostics.size(), 303 0, 304 "Expected no %s diagnostics.\n\nDiagnostics:\n%s", 305 allKindsString, 306 TraceDiagnosticListRepresentation.getInstance().toStringOf(actualDiagnostics) 307 ); 308 } 309 310 return myself; 311 } 312 313 /** 314 * Filter diagnostics by a given predicate and return an assertion object that applies to all 315 * diagnostics that match that predicate. 316 * 317 * @param predicate the predicate to match. 318 * @return the assertion object for the diagnostics that match. 319 * @throws NullPointerException if the predicate is null. 320 * @throws AssertionError if the diagnostic list is null. 321 */ 322 public TraceDiagnosticListAssert filteringBy( 323 Predicate<TraceDiagnostic<? extends JavaFileObject>> predicate 324 ) { 325 requireNonNull(predicate, "predicate must not be null"); 326 327 isNotNull(); 328 329 return actual 330 .stream() 331 .filter(predicate) 332 .collect(Collectors.collectingAndThen( 333 Collectors.toUnmodifiableList(), 334 TraceDiagnosticListAssert::new 335 )); 336 } 337 338 @Override 339 protected TraceDiagnosticAssert toAssert( 340 TraceDiagnostic<? extends JavaFileObject> value, 341 String description 342 ) { 343 return new TraceDiagnosticAssert(value).describedAs(description); 344 } 345 346 @Override 347 protected TraceDiagnosticListAssert newAbstractIterableAssert( 348 Iterable<? extends TraceDiagnostic<? extends JavaFileObject>> iterable 349 ) { 350 var list = new ArrayList<TraceDiagnostic<? extends JavaFileObject>>(); 351 iterable.forEach(list::add); 352 return new TraceDiagnosticListAssert(list); 353 } 354 355 private Predicate<TraceDiagnostic<? extends JavaFileObject>> kind(Iterable<Kind> kinds) { 356 var kindsSet = new LinkedHashSet<Kind>(); 357 kinds.forEach(kindsSet::add); 358 359 return diagnostic -> kindsSet.contains(diagnostic.getKind()); 360 } 361}