Commit 3947168e authored by Aleksey Pivovarov's avatar Aleksey Pivovarov
Browse files

icons.gant: add test for icons

parent f0e7dea8
Branches unavailable Tags unavailable
No related merge requests found
Showing with 190 additions and 37 deletions
+190 -37
......@@ -124,5 +124,6 @@
<orderEntry type="module" module-name="remote-servers-git-java" />
<orderEntry type="module" module-name="terminal" />
<orderEntry type="module" module-name="javac-ref-scanner-8" scope="RUNTIME" />
<orderEntry type="module" module-name="platform-build-scripts" scope="TEST" />
</component>
</module>
\ No newline at end of file
......@@ -29,11 +29,12 @@ import java.util.regex.Pattern
internal class ImagePaths(val id: String, val sourceRoot: JpsModuleSourceRoot, val used: Boolean, val deprecated: Boolean) {
var files: MutableMap<ImageType, File> = HashMap()
var ambiguous: Boolean = false
val file: File? get() = files[ImageType.BASIC]
}
internal class ImageCollector(val projectHome: File, val iconsOnly: Boolean = true) {
internal class ImageCollector(val projectHome: File, val iconsOnly: Boolean = true, val ignoreSkipTag: Boolean = false) {
private val result = HashMap <String, ImagePaths>()
private val usedIconsRobots: MutableSet<File> = HashSet()
......@@ -92,8 +93,12 @@ internal class ImageCollector(val projectHome: File, val iconsOnly: Boolean = tr
if (skipped) return
val iconPaths = result.computeIfAbsent(id, { ImagePaths(id, sourceRoot, used, deprecated) })
assert(iconPaths.files[type] == null)
iconPaths.files[type] = file
if (iconPaths.files[type] == null) {
iconPaths.files[type] = file
}
else {
iconPaths.ambiguous = true
}
}
private fun upToProjectHome(dir: File): IconRobotsData {
......@@ -158,7 +163,7 @@ internal class ImageCollector(val projectHome: File, val iconsOnly: Boolean = tr
private val used: MutableSet<Matcher> = HashSet()
private val deprecated: MutableSet<Matcher> = HashSet()
fun isSkipped(file: File): Boolean = matches(file, skip) || parent?.isSkipped(file) ?: false
fun isSkipped(file: File): Boolean = !ignoreSkipTag && (matches(file, skip) || parent?.isSkipped(file) ?: false)
fun isUsed(file: File): Boolean = matches(file, used) || parent?.isUsed(file) ?: false
fun isDeprecated(file: File): Boolean = matches(file, deprecated) || parent?.isDeprecated(file) ?: false
......
......@@ -15,6 +15,7 @@
*/
package com.intellij.build.scripts
import com.intellij.build.scripts.ImageSanityCheckerBase.Severity.*
import com.intellij.build.scripts.ImageType.*
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.jps.model.module.JpsModule
......@@ -22,37 +23,21 @@ import java.awt.Dimension
import java.io.File
import java.util.*
class ImageSanityChecker(val projectHome: File) {
private val infos: StringBuilder = StringBuilder()
private val warnings: StringBuilder = StringBuilder()
abstract class ImageSanityCheckerBase(val projectHome: File, val ignoreSkipTag: Boolean) {
fun check(module: JpsModule) {
val allImages = ImageCollector(projectHome, false).collect(module)
val allImages = ImageCollector(projectHome, false, ignoreSkipTag).collect(module)
val (images, broken) = allImages.partition { it.file != null }
log(warnings, "ERROR: icons without base version found in module", module, broken)
logErrors(Severity.ERROR, "image without base version", module, broken)
checkHaveRetinaVersion(images, module)
checkHaveCompleteIconSet(images, module)
checkHaveValidSize(images, module)
}
fun printInfo() {
if (infos.isNotEmpty()) {
println("")
println(infos)
}
}
fun printWarnings() {
if (warnings.isNotEmpty()) {
println("")
println(warnings)
}
checkAreNotAmbiguous(images, module)
}
private fun checkHaveRetinaVersion(images: List<ImagePaths>, module: JpsModule) {
process(images, infos, "INFO: icons without retina version found in module", module) { image ->
process(images, Severity.INFO, "image without retina version", module) { image ->
val hasRetina = image.files[RETINA] != null
val hasRetinaDarcula = image.files[RETINA_DARCULA] != null
return@process hasRetina || hasRetinaDarcula
......@@ -60,7 +45,7 @@ class ImageSanityChecker(val projectHome: File) {
}
private fun checkHaveCompleteIconSet(images: List<ImagePaths>, module: JpsModule) {
process(images, warnings, "WARNING: icons without complete set of additional icons found in module", module) { image ->
process(images, WARNING, "image without complete set of additional icons", module) { image ->
val hasRetina = image.files[RETINA] != null
val hasDarcula = image.files[DARCULA] != null
val hasRetinaDarcula = image.files[RETINA_DARCULA] != null
......@@ -75,9 +60,15 @@ class ImageSanityChecker(val projectHome: File) {
}
private fun checkHaveValidSize(images: List<ImagePaths>, module: JpsModule) {
process(images, warnings, "WARNING: icons with suspicious size found in module", module) { image ->
val excludedPaths = arrayOf(
"/tips/images/",
"/ide/ui/laf/icons/"
)
process(images, WARNING, "icon with suspicious size", module) { image ->
if (!isIcon(image.file!!)) return@process true
if (FileUtil.normalize(image.file!!.path).contains("/tips/images/")) return@process true
val path = FileUtil.normalize(image.file!!.path)
if (excludedPaths.any { path.contains(it) }) return@process true
val sizes = image.files.mapValues { imageSize(it.value) }
val sizeBasic = sizes[BASIC]!!
......@@ -92,22 +83,68 @@ class ImageSanityChecker(val projectHome: File) {
}
}
private fun process(images: List<ImagePaths>, logger: StringBuilder, message: String, module: JpsModule,
private fun checkAreNotAmbiguous(images: List<ImagePaths>, module: JpsModule) {
process(images, WARNING, "image with ambiguous definition (ex: has both '.png' and '.gif' versions)", module) { image ->
return@process !image.ambiguous
}
}
private fun process(images: List<ImagePaths>, severity: Severity, message: String, module: JpsModule,
processor: (ImagePaths) -> Boolean) {
val result = ArrayList<ImagePaths>()
images.forEach {
if (!processor(it)) result.add(it)
}
log(logger, message, module, result)
logErrors(severity, message, module, result)
}
private fun log(logger: StringBuilder, message: String, module: JpsModule, images: Collection<ImagePaths>) {
if (images.isEmpty()) return
logger.append("$message '${module.name}'\n")
images.sortedBy { it.id }.forEach {
private fun logErrors(severity: Severity, message: String, module: JpsModule, images: Collection<ImagePaths>) {
log(severity, message, module, images.map {
val path = it.file ?: it.files.values.first()
logger.append(" ${it.id} - $path\n")
Pair(it.id, path)
})
}
abstract fun log(severity: Severity, message: String, module: JpsModule, images: Collection<Pair<String, File>>)
enum class Severity { INFO, WARNING, ERROR }
}
class ImageSanityChecker(projectHome: File) : ImageSanityCheckerBase(projectHome, false) {
private val infos: StringBuilder = StringBuilder()
private val warnings: StringBuilder = StringBuilder()
fun printInfo() {
if (infos.isNotEmpty()) {
println("")
println(infos)
}
}
fun printWarnings() {
if (warnings.isNotEmpty()) {
println("")
println(warnings)
}
}
override fun log(severity: Severity, message: String, module: JpsModule, images: Collection<Pair<String, File>>) {
val logger = when (severity) {
ERROR -> warnings
WARNING -> warnings
INFO -> infos
}
val prefix = when (severity) {
ERROR -> "ERROR:"
WARNING -> "WARNING:"
INFO -> "INFO:"
}
if (images.isEmpty()) return
logger.append("$prefix $message found in module '${module.name}'\n")
images.sortedBy { it.first }.forEach {
logger.append(" ${it.first} - ${it.second}\n")
}
logger.append("\n")
}
}
\ No newline at end of file
}
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.build.scripts
import com.intellij.openapi.application.PathManager
import org.jetbrains.jps.model.JpsElementFactory
import org.jetbrains.jps.model.module.JpsModule
import org.jetbrains.jps.model.serialization.JpsModelSerializationDataService
import org.jetbrains.jps.model.serialization.JpsProjectLoader
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameter
import org.junit.runners.Parameterized.Parameters
import java.io.File
import java.util.*
import kotlin.comparisons.compareBy
import kotlin.comparisons.thenBy
class CommunityImageResourcesTest : ImageResourcesTestBase() {
companion object {
@JvmStatic
@Parameters(name = "{0}")
fun data(): Collection<Array<Any>> {
return ImageResourcesTestBase.collectBadIcons(false)
}
}
}
@Ignore
class AllImageResourcesTest : ImageResourcesTestBase() {
companion object {
@JvmStatic
@Parameters(name = "{0}")
fun data(): Collection<Array<Any>> {
return ImageResourcesTestBase.collectBadIcons(true)
}
}
}
@RunWith(Parameterized::class)
abstract class ImageResourcesTestBase {
@Parameter(value = 0) lateinit var testName: String
@Parameter(value = 1) lateinit var exception: Throwable
@Test
fun test() {
throw exception
}
companion object {
@JvmStatic
fun collectBadIcons(ignoreSkipTag: Boolean): List<Array<Any>> {
val home = PathManager.getHomePath()
val model = JpsElementFactory.getInstance().createModel()
val pathVariables = JpsModelSerializationDataService.computeAllPathVariables(model.global)
JpsProjectLoader.loadProject(model.project, pathVariables, home)
val modules = model.project.modules
val checker = MyChecker(File(home), ignoreSkipTag)
modules.forEach {
checker.check(it)
}
return checker.collectFailures()
.sortedWith(compareBy<FailedTest> { it.module }.thenBy { it.id }.thenBy { it.message })
.map { arrayOf<Any>(it.getTestName(), it.getException()) }
}
}
}
private class MyChecker(projectHome: File, ignoreSkipTag: Boolean) : ImageSanityCheckerBase(projectHome, ignoreSkipTag) {
private val failures = ArrayList<FailedTest>()
override fun log(severity: ImageSanityCheckerBase.Severity,
message: String,
module: JpsModule,
images: Collection<Pair<String, File>>) {
if (severity == Severity.INFO) return
images.forEach { image ->
failures.add(FailedTest(module.name, message, image.first, image.second.path))
}
}
fun collectFailures(): Collection<FailedTest> {
return failures
}
}
class FailedTest(val module: String, val message: String, val id: String, val path: String) {
fun getTestName(): String = "'${module}' - $id - $message"
fun getException(): Throwable = Exception("${message} - ${path}")
}
\ No newline at end of file
......@@ -4,7 +4,8 @@
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/groovy" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/icons" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/icons/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/icons/tests" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
......@@ -34,5 +35,6 @@
</library>
</orderEntry>
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" scope="TEST" name="JUnit4" level="project"/>
</component>
</module>
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment