blob: 4febb81f81fbc8f5ceed3a20fd02881ded2f45fa [file] [log] [blame]
package android.dawn
import java.net.URL
import java.util.jar.JarFile
import kotlin.reflect.KCallable
import kotlin.reflect.KClass
import kotlin.reflect.KProperty1
import kotlin.reflect.full.companionObjectInstance
import kotlin.reflect.full.primaryConstructor
import kotlin.test.assertContains
import kotlin.test.assertEquals
import kotlin.test.fail
import org.junit.Test
/**
* Yield all classes under the specified namespace.
*/
private fun classNames(namespace: String) = sequence {
// Use the thread's class loader to find all the class resource files.
val classLoader = Thread.currentThread().contextClassLoader!!
for (jarUrl in classLoader.getResources(namespace.replace('.', '/'))) {
if (jarUrl.protocol != "jar") {
continue
}
val url = URL(jarUrl.file.substringBefore('!')) // Trim prefix jar: and suffix !...
// Find the class files inside the jar file.
for (entry in JarFile(url.file).entries()) {
if (entry.name.endsWith(".class")) { // Not every file is a class.
yield(entry.name.removeSuffix(".class").replace('/', '.'))
}
}
}
}
class MappedNamedConstantsTest {
val TYPES_WITH_MAPPED_NAMED_CONSTANTS = arrayOf(
AdapterType::class,
AddressMode::class,
BackendType::class,
BlendFactor::class,
BlendOperation::class,
BufferBindingType::class,
BufferMapState::class,
BufferUsage::class,
CallbackMode::class,
ColorWriteMask::class,
CompareFunction::class,
CompilationInfoRequestStatus::class,
CompilationMessageType::class,
CompositeAlphaMode::class,
CreatePipelineAsyncStatus::class,
CullMode::class,
DeviceLostReason::class,
ErrorFilter::class,
ErrorType::class,
FeatureLevel::class,
FeatureName::class,
FilterMode::class,
FrontFace::class,
IndexFormat::class,
LoadOp::class,
MapAsyncStatus::class,
MapMode::class,
MipmapFilterMode::class,
OptionalBool::class,
PopErrorScopeStatus::class,
PowerPreference::class,
PresentMode::class,
PrimitiveTopology::class,
QueryType::class,
QueueWorkDoneStatus::class,
RequestAdapterStatus::class,
RequestDeviceStatus::class,
SamplerBindingType::class,
ShaderStage::class,
Status::class,
StencilOperation::class,
StorageTextureAccess::class,
StoreOp::class,
SType::class,
SubgroupMatrixComponentType::class,
SurfaceGetCurrentTextureStatus::class,
TextureAspect::class,
TextureDimension::class,
TextureFormat::class,
TextureSampleType::class,
TextureUsage::class,
TextureViewDimension::class,
VertexFormat::class,
VertexStepMode::class,
WaitStatus::class,
WGSLLanguageFeatureName::class
)
/**
* Test that the actual classes in our generated Kotlin are all tested in this unit test,
* and that there are no unexpected new classes.
*
* It's worth hardcoding the list above (rather than just listing the target classes to
* inspect dynamically) because this way, if the number of classes found dynamically were
* to suddenly drop to zero (as in, the code changed and the test filter dropped all the
* classes) then we'd know the test was no longer testing the correct thing. Similarly,
* if the number of classes found dynamically were to suddenly increase, then we'd know
* that the test was no longer testing the correct thing.
*/
@Test
fun testPackageClassesMatchTestTargets() {
val dawnClasses = classNames("android.dawn")
val actual = dawnClasses.filter { clazz ->
isAndroidDawn(clazz) && hasCompanionObjectWithNames(clazz)
}.map { it.removePrefix("android.dawn.") }
val expected = TYPES_WITH_MAPPED_NAMED_CONSTANTS.mapNotNull { it.simpleName }
// Test that the two lists match, throw a useful failure if they don't
actual.forEach { className -> assertContains(expected, className) }
expected.forEach { className -> assertContains(actual, className) }
}
/**
* Test that every class listed above (a) has a names field, and (b) each name is
* mapped to the correct string and instance value.
*/
@Test
fun testMappedConstantsNamesAreCorrect() {
for (clazz in TYPES_WITH_MAPPED_NAMED_CONSTANTS) {
val companionObject = getCompanionObjectOrFail(clazz)
val namesMap = getNamesMapOrFail(companionObject, clazz)
val companionConstants = getCompanionConstants(companionObject)
for ((key, constantName) in namesMap) {
val constantProperty = companionConstants[constantName]
val actual = (constantProperty as KProperty1<*, *>).getter.call(companionObject)
val expected = clazz.primaryConstructor?.call(key)
assertEquals(expected, actual)
}
}
}
private fun isAndroidDawn(clazz: String): Boolean {
return clazz.startsWith("android.dawn") && clazz.count { it == '.' } == 2
}
private fun hasCompanionObjectWithNames(clazz: String): Boolean {
val kClass = Class.forName(clazz).kotlin
val companionObject = kClass.companionObjectInstance
return companionObject != null && companionObject::class.members.any { it.name == "names" }
}
private fun getCompanionConstants(companionObject: Any): Map<String, KCallable<*>> {
return companionObject::class.members.filter { it.isFinal && it is KProperty1<*, *> }
.associateBy { it.name }
}
private fun getCompanionObjectOrFail(clazz: KClass<out Any>): Any {
return clazz.companionObjectInstance
?: fail("No companion object found in ${clazz.simpleName}")
}
private fun getNamesMapOrFail(companionObject: Any, clazz: KClass<out Any>): Map<Int, String> {
val namesProperty = companionObject::class.members.find { it.name == "names" }
?: fail("Property 'names' not found in companion object of ${clazz.simpleName}")
@Suppress("UNCHECKED_CAST")
return namesProperty.call(companionObject) as? Map<Int, String>
?: fail("Property 'names' is not of type Map<Int, String>")
}
}