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