mirror of
https://github.com/corda/corda.git
synced 2025-03-10 22:44:20 +00:00
[EG-440] Add some error codes and the error resource generation tool (#6192)
* [EG-438] First commit of error code interface * [EG-438] Implement error reporter and a few error codes * [EG-438] Add unit tests and default properties files * [EG-438] Add the error table builder * [EG-438] Update initial properties files * [EG-438] Add some Irish tests and the build.gradle * [EG-438] Fall back for aliases and use different resource strategy * [EG-438] Define the URL using a project-specific context * [EG-438] Tidy up initialization code * [EG-438] Add testing to generator and tidy up * [EG-438] Remove direct dependency on core and add own logging config * [EG-438] Fix compiler warnings and tidy up logging * [EG-438] Fix detekt warnings * [EG-438] Improve error messages * [EG-438] Address first set of review comments * [EG-438] Use enums and a builder for the reporter * [EG-438] Address first set of review comments * [EG-438] Use enums and a builder for the reporter * [EG-438] Add kdocs for error resource static methods * [EG-440] Add error code for duplicate CorDapp loading * [EG-438] Handle enums defined with underscores * [EG-440] Add errors for some CorDapp loading scenarios * [EG-440] Finish adding errors for CorDapp loading * [EG-440] Fix up errors in properties files * [EG-440] Start change to error code definition * [EG-440] Update error code definition and add resource generation tool * [EG-440] Tidy up error resource generation tool frontend * [EG-440] Small refactorings and add kdocs * [EG-440] Generate all missing resources * [EG-440] Some refactoring and start writing a test * [EG-440] Update unit test for resource generator * [EG-440] Renaming of various parts of the error tool * [EG-440] Add testing for errors and fix an issue in resource generation * [EG-440] Add a kdoc for context provider API * [EG-440] Remove old code from repository * [EG-440] Address some review comments
This commit is contained in:
parent
ab43238420
commit
ab95aa57a2
@ -43,6 +43,11 @@ class ErrorReporting private constructor(private val localeString: String,
|
|||||||
return ErrorReporting(localeString, location, contextProvider)
|
return ErrorReporting(localeString, location, contextProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the context provider to supply project-specific information about the errors.
|
||||||
|
*
|
||||||
|
* @param contextProvider The context provider to use with error reporting
|
||||||
|
*/
|
||||||
fun withContextProvider(contextProvider: ErrorContextProvider) : ErrorReporting {
|
fun withContextProvider(contextProvider: ErrorContextProvider) : ErrorReporting {
|
||||||
return ErrorReporting(localeString, resourceLocation, contextProvider)
|
return ErrorReporting(localeString, resourceLocation, contextProvider)
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,8 @@ package net.corda.common.logging.errorReporting
|
|||||||
* Namespaces for errors within the node.
|
* Namespaces for errors within the node.
|
||||||
*/
|
*/
|
||||||
enum class NodeNamespaces {
|
enum class NodeNamespaces {
|
||||||
DATABASE
|
DATABASE,
|
||||||
|
CORDAPP
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -17,4 +18,16 @@ enum class NodeDatabaseErrors : ErrorCodes {
|
|||||||
PASSWORD_REQUIRED_FOR_H2;
|
PASSWORD_REQUIRED_FOR_H2;
|
||||||
|
|
||||||
override val namespace = NodeNamespaces.DATABASE.toString()
|
override val namespace = NodeNamespaces.DATABASE.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Errors related to loading of Cordapps
|
||||||
|
*/
|
||||||
|
enum class CordappErrors : ErrorCodes {
|
||||||
|
DUPLICATE_CORDAPPS_INSTALLED,
|
||||||
|
MULTIPLE_CORDAPPS_FOR_FLOW,
|
||||||
|
MISSING_VERSION_ATTRIBUTE,
|
||||||
|
INVALID_VERSION_IDENTIFIER;
|
||||||
|
|
||||||
|
override val namespace = NodeNamespaces.CORDAPP.toString()
|
||||||
}
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
errorTemplate = The CorDapp (name: {0}, file: {1}) is installed multiple times on the node. The following files correspond to the exact same content: {2}
|
||||||
|
shortDescription = A CorDapp has been installed multiple times on the same node.
|
||||||
|
actionsToFix = Investigate the logs to determine the files with duplicate content, and remove one of them from the cordapps directory.
|
||||||
|
aliases = iw8d4e
|
@ -0,0 +1,3 @@
|
|||||||
|
errorTemplate = The CorDapp (name: {0}, file: {1}) is installed multiple times on the node. The following files correspond to the exact same content: {2}
|
||||||
|
shortDescription = A CorDapp has been installed multiple times on the same node.
|
||||||
|
actionsToFix = Investigate the logs to determine the files with duplicate content, and remove one of them from the cordapps directory.
|
@ -0,0 +1,4 @@
|
|||||||
|
errorTemplate = Version identifier ({0}) for attribute {1} must be a whole number starting from 1.
|
||||||
|
shortDescription = A version attribute was specified in the CorDapp manifest with an invalid value. The value must be a whole number, and it must be greater than or equal to 1.
|
||||||
|
actionsToFix = Investigate the logs to find the invalid attribute, and change the attribute value to be valid (a whole number greater than or equal to 1).
|
||||||
|
aliases =
|
@ -0,0 +1,4 @@
|
|||||||
|
errorTemplate = Version identifier ({0}) for attribute {1} must be a whole number starting from 1.
|
||||||
|
shortDescription = A version attribute was specified in the CorDapp manifest with an invalid value. The value must be a whole number, and it must be greater than or equal to 1.
|
||||||
|
actionsToFix = Investigate the logs to find the invalid attribute, and change the attribute value to be valid (a whole number greater than or equal to 1).
|
||||||
|
aliases =
|
@ -0,0 +1,4 @@
|
|||||||
|
errorTemplate = Target versionId attribute {0} not specified. Please specify a whole number starting from 1.
|
||||||
|
shortDescription = A required version attribute was not specified in the manifest of the CorDapp JAR.
|
||||||
|
actionsToFix = Investigate the logs to find out which version attribute has not been specified, and add that version attribute to the CorDapp manifest.
|
||||||
|
aliases =
|
@ -0,0 +1,3 @@
|
|||||||
|
errorTemplate = Target versionId attribute {0} not specified. Please specify a whole number starting from 1.
|
||||||
|
shortDescription = A required version attribute was not specified in the manifest of the CorDapp JAR.
|
||||||
|
actionsToFix = Investigate the logs to find out which version attribute has not been specified, and add that version attribute to the CorDapp manifest.
|
@ -0,0 +1,4 @@
|
|||||||
|
errorTemplate = There are multiple CorDapp JARs on the classpath for the flow {0}: [{1}]
|
||||||
|
shortDescription = Multiple CorDapp JARs on the classpath define the same flow class. As a result, the platform will not know which version of the flow to start when the flow is invoked.
|
||||||
|
actionsToFix = Investigate the logs to find out which CorDapp JARs define the same flow classes. The developers of these apps will need to resolve the clash.
|
||||||
|
aliases =
|
@ -0,0 +1,4 @@
|
|||||||
|
errorTemplate = There are multiple CorDapp JARs on the classpath for the flow {0}: [{1}]
|
||||||
|
shortDescription = Multiple CorDapp JARs on the classpath define the same flow class. As a result, the platform will not know which version of the flow to start when the flow is invoked.
|
||||||
|
actionsToFix = Investigate the logs to find out which CorDapp JARs define the same flow classes. The developers of these apps will need to resolve the clash.
|
||||||
|
aliases =
|
@ -0,0 +1,12 @@
|
|||||||
|
package net.corda.commmon.logging.errorReporting
|
||||||
|
|
||||||
|
import net.corda.common.logging.errorReporting.CordappErrors
|
||||||
|
|
||||||
|
class CordappErrorsTest : ErrorCodeTest<CordappErrors>(CordappErrors::class.java, true) {
|
||||||
|
override val dataForCodes = mapOf(
|
||||||
|
CordappErrors.MISSING_VERSION_ATTRIBUTE to listOf("test-attribute"),
|
||||||
|
CordappErrors.INVALID_VERSION_IDENTIFIER to listOf(-1, "test-attribute"),
|
||||||
|
CordappErrors.MULTIPLE_CORDAPPS_FOR_FLOW to listOf("MyTestFlow", "Jar 1, Jar 2"),
|
||||||
|
CordappErrors.DUPLICATE_CORDAPPS_INSTALLED to listOf("TestCordapp", "testapp.jar", "testapp2.jar")
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package net.corda.commmon.logging.errorReporting
|
||||||
|
|
||||||
|
import net.corda.common.logging.errorReporting.NodeDatabaseErrors
|
||||||
|
import java.net.InetAddress
|
||||||
|
|
||||||
|
class DatabaseErrorsTest : ErrorCodeTest<NodeDatabaseErrors>(NodeDatabaseErrors::class.java) {
|
||||||
|
override val dataForCodes = mapOf(
|
||||||
|
NodeDatabaseErrors.COULD_NOT_CONNECT to listOf<Any>(),
|
||||||
|
NodeDatabaseErrors.FAILED_STARTUP to listOf(),
|
||||||
|
NodeDatabaseErrors.MISSING_DRIVER to listOf(),
|
||||||
|
NodeDatabaseErrors.PASSWORD_REQUIRED_FOR_H2 to listOf(InetAddress.getLocalHost())
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
package net.corda.commmon.logging.errorReporting
|
||||||
|
|
||||||
|
import junit.framework.TestCase.assertFalse
|
||||||
|
import net.corda.common.logging.errorReporting.ErrorCode
|
||||||
|
import net.corda.common.logging.errorReporting.ErrorCodes
|
||||||
|
import net.corda.common.logging.errorReporting.ErrorResource
|
||||||
|
import net.corda.common.logging.errorReporting.ResourceBundleProperties
|
||||||
|
import org.junit.Test
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility for testing that error code resource files behave as expected.
|
||||||
|
*
|
||||||
|
* This allows for testing that error messages are printed correctly if they are provided the correct parameters. The test will fail if any
|
||||||
|
* of the parameters of the template are not filled in.
|
||||||
|
*
|
||||||
|
* To use, override the `dataForCodes` with a map from an error code enum value to a list of parameters the message template takes. If any
|
||||||
|
* are missed, the test will fail.
|
||||||
|
*
|
||||||
|
* `printProperties`, if set to true, will print the properties out the resource files, with the error message filled in. This allows the
|
||||||
|
* message to be inspected.
|
||||||
|
*/
|
||||||
|
abstract class ErrorCodeTest<T>(private val clazz: Class<T>,
|
||||||
|
private val printProperties: Boolean = false) where T: Enum<T>, T: ErrorCodes {
|
||||||
|
|
||||||
|
abstract val dataForCodes: Map<T, List<Any>>
|
||||||
|
|
||||||
|
private class TestError<T>(override val code: T,
|
||||||
|
override val parameters: List<Any>) : ErrorCode<T> where T: Enum<T>, T: ErrorCodes
|
||||||
|
|
||||||
|
@Test(timeout = 300_000)
|
||||||
|
fun `test error codes`() {
|
||||||
|
for ((code, params) in dataForCodes) {
|
||||||
|
val error = TestError(code, params)
|
||||||
|
val resource = ErrorResource.fromErrorCode(error, "error-codes", Locale.forLanguageTag("en-US"))
|
||||||
|
val message = resource.getErrorMessage(error.parameters.toTypedArray())
|
||||||
|
assertFalse(
|
||||||
|
"The error message reported for code $code contains missing parameters",
|
||||||
|
message.contains("\\{.*}".toRegex())
|
||||||
|
)
|
||||||
|
val otherProperties = Triple(resource.shortDescription, resource.actionsToFix, resource.aliases)
|
||||||
|
if (printProperties) {
|
||||||
|
println("Data for $code")
|
||||||
|
println("Error Message = $message")
|
||||||
|
println("${ResourceBundleProperties.SHORT_DESCRIPTION} = ${otherProperties.first}")
|
||||||
|
println("${ResourceBundleProperties.ACTIONS_TO_FIX} = ${otherProperties.second}")
|
||||||
|
println("${ResourceBundleProperties.ALIASES} = ${otherProperties.third}")
|
||||||
|
println("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 300_000)
|
||||||
|
fun `ensure all error codes tested`() {
|
||||||
|
val expected = clazz.enumConstants.toSet()
|
||||||
|
val actual = dataForCodes.keys.toSet()
|
||||||
|
val missing = expected - actual
|
||||||
|
assertTrue(missing.isEmpty(), "The following codes have not been tested: $missing")
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,10 @@ package net.corda.node.internal.cordapp
|
|||||||
|
|
||||||
import io.github.classgraph.ClassGraph
|
import io.github.classgraph.ClassGraph
|
||||||
import io.github.classgraph.ScanResult
|
import io.github.classgraph.ScanResult
|
||||||
|
import net.corda.common.logging.errorReporting.CordappErrors
|
||||||
|
import net.corda.common.logging.errorReporting.ErrorCode
|
||||||
|
import net.corda.common.logging.errorReporting.NodeDatabaseErrors
|
||||||
|
import net.corda.common.logging.errorReporting.NodeNamespaces
|
||||||
import net.corda.core.cordapp.Cordapp
|
import net.corda.core.cordapp.Cordapp
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.sha256
|
import net.corda.core.crypto.sha256
|
||||||
@ -144,9 +148,7 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
|||||||
val duplicateCordapps = registeredCordapps.filter { it.jarHash == cordapp.jarHash }.toSet()
|
val duplicateCordapps = registeredCordapps.filter { it.jarHash == cordapp.jarHash }.toSet()
|
||||||
|
|
||||||
if (duplicateCordapps.isNotEmpty()) {
|
if (duplicateCordapps.isNotEmpty()) {
|
||||||
throw IllegalStateException("The CorDapp (name: ${cordapp.info.shortName}, file: ${cordapp.name}) " +
|
throw DuplicateCordappsInstalledException(cordapp, duplicateCordapps)
|
||||||
"is installed multiple times on the node. The following files correspond to the exact same content: " +
|
|
||||||
"${duplicateCordapps.map { it.name }}")
|
|
||||||
}
|
}
|
||||||
if (registeredClassName in contractClasses) {
|
if (registeredClassName in contractClasses) {
|
||||||
throw IllegalStateException("More than one CorDapp installed on the node for contract $registeredClassName. " +
|
throw IllegalStateException("More than one CorDapp installed on the node for contract $registeredClassName. " +
|
||||||
@ -227,12 +229,21 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
|||||||
|
|
||||||
private fun parseVersion(versionStr: String?, attributeName: String): Int {
|
private fun parseVersion(versionStr: String?, attributeName: String): Int {
|
||||||
if (versionStr == null) {
|
if (versionStr == null) {
|
||||||
throw CordappInvalidVersionException("Target versionId attribute $attributeName not specified. Please specify a whole number starting from 1.")
|
throw CordappInvalidVersionException(
|
||||||
|
"Target versionId attribute $attributeName not specified. Please specify a whole number starting from 1.",
|
||||||
|
CordappErrors.MISSING_VERSION_ATTRIBUTE,
|
||||||
|
listOf(attributeName))
|
||||||
}
|
}
|
||||||
val version = versionStr.toIntOrNull()
|
val version = versionStr.toIntOrNull()
|
||||||
?: throw CordappInvalidVersionException("Version identifier ($versionStr) for attribute $attributeName must be a whole number starting from 1.")
|
?: throw CordappInvalidVersionException(
|
||||||
|
"Version identifier ($versionStr) for attribute $attributeName must be a whole number starting from 1.",
|
||||||
|
CordappErrors.INVALID_VERSION_IDENTIFIER,
|
||||||
|
listOf(versionStr, attributeName))
|
||||||
if (version < 1) {
|
if (version < 1) {
|
||||||
throw CordappInvalidVersionException("Target versionId ($versionStr) for attribute $attributeName must not be smaller than 1.")
|
throw CordappInvalidVersionException(
|
||||||
|
"Target versionId ($versionStr) for attribute $attributeName must not be smaller than 1.",
|
||||||
|
CordappErrors.INVALID_VERSION_IDENTIFIER,
|
||||||
|
listOf(versionStr, attributeName))
|
||||||
}
|
}
|
||||||
return version
|
return version
|
||||||
}
|
}
|
||||||
@ -403,12 +414,34 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
|
|||||||
/**
|
/**
|
||||||
* Thrown when scanning CorDapps.
|
* Thrown when scanning CorDapps.
|
||||||
*/
|
*/
|
||||||
class MultipleCordappsForFlowException(message: String) : Exception(message)
|
class MultipleCordappsForFlowException(
|
||||||
|
message: String,
|
||||||
|
flowName: String,
|
||||||
|
jars: String
|
||||||
|
) : Exception(message), ErrorCode<CordappErrors> {
|
||||||
|
override val code = CordappErrors.MULTIPLE_CORDAPPS_FOR_FLOW
|
||||||
|
override val parameters = listOf(flowName, jars)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thrown if an exception occurs whilst parsing version identifiers within cordapp configuration
|
* Thrown if an exception occurs whilst parsing version identifiers within cordapp configuration
|
||||||
*/
|
*/
|
||||||
class CordappInvalidVersionException(msg: String) : Exception(msg)
|
class CordappInvalidVersionException(
|
||||||
|
msg: String,
|
||||||
|
override val code: CordappErrors,
|
||||||
|
override val parameters: List<Any> = listOf()
|
||||||
|
) : Exception(msg), ErrorCode<CordappErrors>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown if duplicate CorDapps are installed on the node
|
||||||
|
*/
|
||||||
|
class DuplicateCordappsInstalledException(app: Cordapp, duplicates: Set<Cordapp>)
|
||||||
|
: IllegalStateException("The CorDapp (name: ${app.info.shortName}, file: ${app.name}) " +
|
||||||
|
"is installed multiple times on the node. The following files correspond to the exact same content: " +
|
||||||
|
"${duplicates.map { it.name }}"), ErrorCode<CordappErrors> {
|
||||||
|
override val code = CordappErrors.DUPLICATE_CORDAPPS_INSTALLED
|
||||||
|
override val parameters = listOf(app.info.shortName, app.name, duplicates.map { it.name })
|
||||||
|
}
|
||||||
|
|
||||||
abstract class CordappLoaderTemplate : CordappLoader {
|
abstract class CordappLoaderTemplate : CordappLoader {
|
||||||
|
|
||||||
@ -436,7 +469,9 @@ abstract class CordappLoaderTemplate : CordappLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw MultipleCordappsForFlowException("There are multiple CorDapp JARs on the classpath for flow " +
|
throw MultipleCordappsForFlowException("There are multiple CorDapp JARs on the classpath for flow " +
|
||||||
"${entry.value.first().first.name}: [ ${entry.value.joinToString { it.second.jarPath.toString() }} ].")
|
"${entry.value.first().first.name}: [ ${entry.value.joinToString { it.second.jarPath.toString() }} ].",
|
||||||
|
entry.value.first().first.name,
|
||||||
|
entry.value.joinToString { it.second.jarPath.toString() })
|
||||||
}
|
}
|
||||||
entry.value.single().second
|
entry.value.single().second
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package net.corda.node.internal.cordapp
|
package net.corda.node.internal.cordapp
|
||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
|
import net.corda.common.logging.errorReporting.CordappErrors
|
||||||
import net.corda.core.flows.*
|
import net.corda.core.flows.*
|
||||||
import net.corda.node.VersionInfo
|
import net.corda.node.VersionInfo
|
||||||
import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES
|
import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES
|
||||||
|
@ -108,5 +108,5 @@ include 'serialization-deterministic'
|
|||||||
|
|
||||||
include 'tools:checkpoint-agent'
|
include 'tools:checkpoint-agent'
|
||||||
include 'detekt-plugins'
|
include 'detekt-plugins'
|
||||||
include 'tools:error-page-builder'
|
include 'tools:error-tool'
|
||||||
|
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
group 'net.corda'
|
|
||||||
version '4.5-SNAPSHOT'
|
|
||||||
|
|
||||||
apply plugin: 'kotlin'
|
apply plugin: 'kotlin'
|
||||||
apply plugin: 'com.github.johnrengelman.shadow'
|
apply plugin: 'com.github.johnrengelman.shadow'
|
||||||
|
|
||||||
@ -12,9 +9,9 @@ dependencies {
|
|||||||
implementation project(":common-logging")
|
implementation project(":common-logging")
|
||||||
implementation project(":tools:cliutils")
|
implementation project(":tools:cliutils")
|
||||||
implementation "info.picocli:picocli:$picocli_version"
|
implementation "info.picocli:picocli:$picocli_version"
|
||||||
testCompile group: 'junit', name: 'junit', version: '4.12'
|
|
||||||
|
|
||||||
implementation "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
|
implementation "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
|
||||||
|
|
||||||
|
testCompile "junit:junit:4.12"
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
@ -23,10 +20,12 @@ jar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
shadowJar {
|
shadowJar {
|
||||||
baseName = "corda-tools-error-page-builder"
|
baseName = "corda-tools-error-utils"
|
||||||
manifest {
|
manifest {
|
||||||
attributes(
|
attributes(
|
||||||
'Main-Class': "net.corda.errorPageBuilder.ErrorPageBuilderKt"
|
'Main-Class': "net.corda.errorUtilities.ErrorToolKt"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assemble.dependsOn shadowJar
|
@ -0,0 +1,45 @@
|
|||||||
|
package net.corda.errorUtilities
|
||||||
|
|
||||||
|
import java.net.URLClassLoader
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class for reading and processing error code resource bundles from a given directory.
|
||||||
|
*/
|
||||||
|
class ErrorResourceUtilities {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val ERROR_INFO_RESOURCE_REGEX= ".*ErrorInfo.*".toRegex()
|
||||||
|
private val DEFAULT_RESOURCE_FILE_REGEX = "[^_]*\\.properties".toRegex()
|
||||||
|
private val PROPERTIES_FILE_REGEX = ".*\\.properties".toRegex()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all resource bundle names in a given directory
|
||||||
|
*/
|
||||||
|
fun listResourceNames(location: Path) : List<String> {
|
||||||
|
return location.toFile().walkTopDown().filter {
|
||||||
|
it.name.matches(DEFAULT_RESOURCE_FILE_REGEX) && !it.name.matches(ERROR_INFO_RESOURCE_REGEX)
|
||||||
|
}.map {
|
||||||
|
it.nameWithoutExtension
|
||||||
|
}.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all resource files in a given directory
|
||||||
|
*/
|
||||||
|
fun listResourceFiles(location: Path) : List<String> {
|
||||||
|
return location.toFile().walkTopDown().filter {
|
||||||
|
it.name.matches(PROPERTIES_FILE_REGEX)
|
||||||
|
}.map { it.name }.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a classloader with all URLs in a given directory
|
||||||
|
*/
|
||||||
|
fun loaderFromDirectory(location: Path) : URLClassLoader {
|
||||||
|
val urls = arrayOf(location.toUri().toURL())
|
||||||
|
val sysLoader = ClassLoader.getSystemClassLoader()
|
||||||
|
return URLClassLoader(urls, sysLoader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package net.corda.errorUtilities
|
||||||
|
|
||||||
|
import net.corda.cliutils.CordaCliWrapper
|
||||||
|
import net.corda.cliutils.ExitCodes
|
||||||
|
import net.corda.cliutils.start
|
||||||
|
import net.corda.errorUtilities.docsTable.DocsTableCLI
|
||||||
|
import net.corda.errorUtilities.resourceGenerator.ResourceGeneratorCLI
|
||||||
|
|
||||||
|
fun main(args: Array<String>) = ErrorTool().start(args)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entry point for the error utilities.
|
||||||
|
*
|
||||||
|
* By itself, this doesn't do anything - instead one of the subcommands should be invoked.
|
||||||
|
*/
|
||||||
|
class ErrorTool : CordaCliWrapper("error-utils", "Utilities for working with error codes and error reporting") {
|
||||||
|
|
||||||
|
private val errorPageBuilder = DocsTableCLI()
|
||||||
|
private val errorResourceGenerator = ResourceGeneratorCLI()
|
||||||
|
|
||||||
|
override fun additionalSubCommands() = setOf(errorPageBuilder, errorResourceGenerator)
|
||||||
|
|
||||||
|
override fun runProgram(): Int {
|
||||||
|
println("No subcommand specified - please invoke one of the subcommands.")
|
||||||
|
printHelp()
|
||||||
|
return ExitCodes.FAILURE
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package net.corda.errorUtilities
|
||||||
|
|
||||||
|
import java.lang.IllegalArgumentException
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common functions to use among multiple of the error code subcommands
|
||||||
|
*/
|
||||||
|
class ErrorToolCLIUtilities {
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Checks that a directory provided through Picocli exists.
|
||||||
|
*/
|
||||||
|
fun checkDirectory(dir: Path?, expectedContents: String) : Path {
|
||||||
|
return dir?.also {
|
||||||
|
require(Files.exists(it)) {
|
||||||
|
"Directory $it does not exist. Please specify a valid direction for $expectedContents"
|
||||||
|
}
|
||||||
|
} ?: throw IllegalArgumentException("No location specified for $expectedContents. Please specify a directory for $expectedContents.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package net.corda.errorUtilities
|
||||||
|
|
||||||
|
abstract class ErrorToolException(msg: String, cause: Exception? = null) : Exception(msg, cause)
|
||||||
|
|
||||||
|
class ClassDoesNotExistException(classname: String)
|
||||||
|
: ErrorToolException("The class $classname could not be found in the provided JAR. " +
|
||||||
|
"Check that the correct fully qualified name has been provided and the JAR file is the correct one for this class.")
|
@ -1,22 +1,24 @@
|
|||||||
package net.corda.errorPageBuilder
|
package net.corda.errorUtilities.docsTable
|
||||||
|
|
||||||
import net.corda.cliutils.CordaCliWrapper
|
import net.corda.cliutils.CordaCliWrapper
|
||||||
import net.corda.cliutils.ExitCodes
|
import net.corda.cliutils.ExitCodes
|
||||||
import net.corda.cliutils.start
|
import net.corda.errorUtilities.ErrorToolCLIUtilities
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import picocli.CommandLine
|
import picocli.CommandLine
|
||||||
import java.io.File
|
|
||||||
import java.lang.IllegalArgumentException
|
import java.lang.IllegalArgumentException
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
/**
|
||||||
val builder = ErrorPageBuilder()
|
* Error tool sub-command for generating the documentation for error codes.
|
||||||
builder.start(args)
|
*
|
||||||
}
|
* The command needs a location to output the documentation to and a directory containing the resource files. From this, it generates a
|
||||||
|
* Markdown table with all defined error codes.
|
||||||
class ErrorPageBuilder : CordaCliWrapper("error-page-builder", "Builds the error table for the error codes page") {
|
*
|
||||||
|
* In the event that the file already exists, the tool will report an error and exit.
|
||||||
|
*/
|
||||||
|
class DocsTableCLI : CordaCliWrapper("build-docs", "Builds the error table for the error codes page") {
|
||||||
|
|
||||||
@CommandLine.Parameters(
|
@CommandLine.Parameters(
|
||||||
index = "0",
|
index = "0",
|
||||||
@ -42,43 +44,27 @@ class ErrorPageBuilder : CordaCliWrapper("error-page-builder", "Builds the error
|
|||||||
var localeTag: String? = null
|
var localeTag: String? = null
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val logger = LoggerFactory.getLogger(ErrorPageBuilder::class.java)
|
private val logger = LoggerFactory.getLogger(DocsTableCLI::class.java)
|
||||||
private const val ERROR_CODES_FILE = "error-codes.md"
|
private const val ERROR_CODES_FILE = "error-codes.md"
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getOutputFile() : File {
|
|
||||||
return outputDir?.let {
|
|
||||||
require(Files.exists(it)) {
|
|
||||||
"Directory $it does not exist. Please specify a valid directory to write output to."
|
|
||||||
}
|
|
||||||
val outputPath = it.resolve(ERROR_CODES_FILE)
|
|
||||||
require(Files.notExists(outputPath)) {
|
|
||||||
"Output file $outputPath exists, please remove it and run again."
|
|
||||||
}
|
|
||||||
outputPath.toFile()
|
|
||||||
} ?: throw IllegalArgumentException("Directory not specified. Please specify a valid directory to write output to.")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getResourceDir() : Path {
|
|
||||||
return resourceLocation?.also {
|
|
||||||
require(Files.exists(it)) {
|
|
||||||
"Resource location $it does not exist. Please specify a valid location for error code resources"
|
|
||||||
}
|
|
||||||
} ?: throw IllegalArgumentException("Resource location not specified. Please specify a resource location.")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun runProgram(): Int {
|
override fun runProgram(): Int {
|
||||||
val locale = if (localeTag != null) Locale.forLanguageTag(localeTag) else Locale.getDefault()
|
val locale = if (localeTag != null) Locale.forLanguageTag(localeTag) else Locale.getDefault()
|
||||||
val (outputFile, resources) = try {
|
val (outputFile, resources) = try {
|
||||||
Pair(getOutputFile(), getResourceDir())
|
val output = ErrorToolCLIUtilities.checkDirectory(outputDir, "output file")
|
||||||
|
val outputPath = output.resolve(ERROR_CODES_FILE)
|
||||||
|
require(Files.notExists(outputPath)) {
|
||||||
|
"Output file $outputPath exists, please remove it and run again."
|
||||||
|
}
|
||||||
|
Pair(outputPath, ErrorToolCLIUtilities.checkDirectory(resourceLocation, "resource bundle files"))
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
logger.error(e.message, e)
|
logger.error(e.message, e)
|
||||||
return ExitCodes.FAILURE
|
return ExitCodes.FAILURE
|
||||||
}
|
}
|
||||||
val tableGenerator = ErrorTableGenerator(resources.toFile(), locale)
|
val tableGenerator = DocsTableGenerator(resources, locale)
|
||||||
try {
|
try {
|
||||||
val table = tableGenerator.generateMarkdown()
|
val table = tableGenerator.generateMarkdown()
|
||||||
outputFile.writeText(table)
|
outputFile.toFile().writeText(table)
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
logger.error(e.message, e)
|
logger.error(e.message, e)
|
||||||
return ExitCodes.FAILURE
|
return ExitCodes.FAILURE
|
@ -1,13 +1,16 @@
|
|||||||
package net.corda.errorPageBuilder
|
package net.corda.errorUtilities.docsTable
|
||||||
|
|
||||||
import net.corda.common.logging.errorReporting.ErrorResource
|
import net.corda.common.logging.errorReporting.ErrorResource
|
||||||
import java.io.File
|
import net.corda.errorUtilities.ErrorResourceUtilities
|
||||||
import java.lang.IllegalArgumentException
|
import java.lang.IllegalArgumentException
|
||||||
import java.net.URLClassLoader
|
import java.nio.file.Path
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class ErrorTableGenerator(private val resourceLocation: File,
|
/**
|
||||||
private val locale: Locale) {
|
* Generate the documentation table given a resource file location set.
|
||||||
|
*/
|
||||||
|
class DocsTableGenerator(private val resourceLocation: Path,
|
||||||
|
private val locale: Locale) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val ERROR_CODE_HEADING = "codeHeading"
|
private const val ERROR_CODE_HEADING = "codeHeading"
|
||||||
@ -15,7 +18,6 @@ class ErrorTableGenerator(private val resourceLocation: File,
|
|||||||
private const val DESCRIPTION_HEADING = "descriptionHeading"
|
private const val DESCRIPTION_HEADING = "descriptionHeading"
|
||||||
private const val TO_FIX_HEADING = "toFixHeading"
|
private const val TO_FIX_HEADING = "toFixHeading"
|
||||||
private const val ERROR_HEADINGS_BUNDLE = "ErrorPageHeadings"
|
private const val ERROR_HEADINGS_BUNDLE = "ErrorPageHeadings"
|
||||||
private const val ERROR_INFO_RESOURCE = "ErrorInfo.properties"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getHeading(heading: String) : String {
|
private fun getHeading(heading: String) : String {
|
||||||
@ -23,23 +25,10 @@ class ErrorTableGenerator(private val resourceLocation: File,
|
|||||||
return resource.getString(heading)
|
return resource.getString(heading)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun listResources() : Iterator<String> {
|
|
||||||
return resourceLocation.walkTopDown().filter {
|
|
||||||
it.name.matches("[^_]*\\.properties".toRegex()) && !it.name.matches(ERROR_INFO_RESOURCE.toRegex())
|
|
||||||
}.map {
|
|
||||||
it.nameWithoutExtension
|
|
||||||
}.iterator()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createLoader() : ClassLoader {
|
|
||||||
val urls = resourceLocation.walkTopDown().map { it.toURI().toURL() }.asIterable().toList().toTypedArray()
|
|
||||||
return URLClassLoader(urls)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun generateTable() : List<List<String>> {
|
private fun generateTable() : List<List<String>> {
|
||||||
val table = mutableListOf<List<String>>()
|
val table = mutableListOf<List<String>>()
|
||||||
val loader = createLoader()
|
val loader = ErrorResourceUtilities.loaderFromDirectory(resourceLocation)
|
||||||
for (resource in listResources()) {
|
for (resource in ErrorResourceUtilities.listResourceNames(resourceLocation)) {
|
||||||
val errorResource = ErrorResource.fromLoader(resource, loader, locale)
|
val errorResource = ErrorResource.fromLoader(resource, loader, locale)
|
||||||
table.add(listOf(resource, errorResource.aliases, errorResource.shortDescription, errorResource.actionsToFix))
|
table.add(listOf(resource, errorResource.aliases, errorResource.shortDescription, errorResource.actionsToFix))
|
||||||
}
|
}
|
||||||
@ -59,7 +48,7 @@ class ErrorTableGenerator(private val resourceLocation: File,
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun generateMarkdown() : String {
|
fun generateMarkdown() : String {
|
||||||
if (!resourceLocation.exists()) throw IllegalArgumentException("Directory $resourceLocation does not exist.")
|
if (!resourceLocation.toFile().exists()) throw IllegalArgumentException("Directory $resourceLocation does not exist.")
|
||||||
val tableData = generateTable()
|
val tableData = generateTable()
|
||||||
return formatTable(tableData)
|
return formatTable(tableData)
|
||||||
}
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
package net.corda.errorUtilities.resourceGenerator
|
||||||
|
|
||||||
|
import net.corda.common.logging.errorReporting.ErrorCodes
|
||||||
|
import net.corda.common.logging.errorReporting.ResourceBundleProperties
|
||||||
|
import net.corda.errorUtilities.ClassDoesNotExistException
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a set of resource files from an enumeration of error codes.
|
||||||
|
*/
|
||||||
|
class ResourceGenerator(private val locales: List<Locale>) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
internal const val MESSAGE_TEMPLATE_DEFAULT = "<Message template>"
|
||||||
|
internal const val SHORT_DESCRIPTION_DEFAULT = "<Short description>"
|
||||||
|
internal const val ACTIONS_TO_FIX_DEFAULT = "<Actions to fix>"
|
||||||
|
internal const val ALIASES_DEFAULT = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createResourceFile(name: String, location: Path) {
|
||||||
|
val file = location.resolve(name)
|
||||||
|
val text = """
|
||||||
|
|${ResourceBundleProperties.MESSAGE_TEMPLATE} = $MESSAGE_TEMPLATE_DEFAULT
|
||||||
|
|${ResourceBundleProperties.SHORT_DESCRIPTION} = $SHORT_DESCRIPTION_DEFAULT
|
||||||
|
|${ResourceBundleProperties.ACTIONS_TO_FIX} = $ACTIONS_TO_FIX_DEFAULT
|
||||||
|
|${ResourceBundleProperties.ALIASES} = $ALIASES_DEFAULT
|
||||||
|
""".trimMargin()
|
||||||
|
file.toFile().writeText(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a set of resource files in the given location.
|
||||||
|
*
|
||||||
|
* @param resources The resource file names to create
|
||||||
|
* @param resourceLocation The location to create the resource files
|
||||||
|
*/
|
||||||
|
fun createResources(resources: List<String>, resourceLocation: Path) {
|
||||||
|
for (resource in resources) {
|
||||||
|
createResourceFile(resource, resourceLocation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun definedCodes(classes: List<String>, loader: ClassLoader) : List<String> {
|
||||||
|
return classes.flatMap {
|
||||||
|
val clazz = try {
|
||||||
|
loader.loadClass(it)
|
||||||
|
} catch (e: ClassNotFoundException) {
|
||||||
|
throw ClassDoesNotExistException(it)
|
||||||
|
}
|
||||||
|
if (ErrorCodes::class.java.isAssignableFrom(clazz) && clazz != ErrorCodes::class.java) {
|
||||||
|
val namespace = (clazz.enumConstants.first() as ErrorCodes).namespace.toLowerCase()
|
||||||
|
clazz.enumConstants.map { code -> "${namespace}-${code.toString().toLowerCase().replace("_", "-")}"}
|
||||||
|
} else {
|
||||||
|
listOf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getExpectedResources(codes: List<String>) : List<String> {
|
||||||
|
return codes.flatMap {
|
||||||
|
val localeResources = locales.map { locale -> "${it}_${locale.toLanguageTag().replace("-", "_")}.properties"}
|
||||||
|
localeResources + "$it.properties"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate what resource files are missing from a set of resource files, given a set of error codes.
|
||||||
|
*
|
||||||
|
* @param classes The classes to generate resource files for
|
||||||
|
* @param resourceFiles The list of resource files
|
||||||
|
*/
|
||||||
|
fun calculateMissingResources(classes: List<String>, resourceFiles: List<String>, loader: ClassLoader) : List<String> {
|
||||||
|
val codes = definedCodes(classes, loader)
|
||||||
|
val expected = getExpectedResources(codes)
|
||||||
|
val missing = expected - resourceFiles.toSet()
|
||||||
|
return missing.toList()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package net.corda.errorUtilities.resourceGenerator
|
||||||
|
|
||||||
|
import net.corda.cliutils.CordaCliWrapper
|
||||||
|
import net.corda.cliutils.ExitCodes
|
||||||
|
import net.corda.errorUtilities.ErrorResourceUtilities
|
||||||
|
import net.corda.errorUtilities.ErrorToolCLIUtilities
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import picocli.CommandLine
|
||||||
|
import java.lang.IllegalArgumentException
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subcommand for generating resource bundles from error codes.
|
||||||
|
*
|
||||||
|
* This subcommand takes a directory containing built class files that define the enumerations of codes, and a directory containing any
|
||||||
|
* existing resource bundles. From this, it generates any missing resource files with the properties specified. The data under these
|
||||||
|
* properties should then be filled in by hand.
|
||||||
|
*/
|
||||||
|
class ResourceGeneratorCLI : CordaCliWrapper(
|
||||||
|
"generate-resources",
|
||||||
|
"Generate any missing resource files for a set of error codes"
|
||||||
|
) {
|
||||||
|
|
||||||
|
@CommandLine.Parameters(
|
||||||
|
index = "0",
|
||||||
|
paramLabel = "JAR_FILE",
|
||||||
|
arity = "1",
|
||||||
|
description = ["JAR file containing class files of the error code definitions"]
|
||||||
|
)
|
||||||
|
var jarFile: Path? = null
|
||||||
|
|
||||||
|
@CommandLine.Parameters(
|
||||||
|
index = "1",
|
||||||
|
paramLabel = "RESOURCE_DIR",
|
||||||
|
arity = "1",
|
||||||
|
description = ["Directory containing resource bundles for the error codes"]
|
||||||
|
)
|
||||||
|
var resourceDir: Path? = null
|
||||||
|
|
||||||
|
@CommandLine.Parameters(
|
||||||
|
index="2..*",
|
||||||
|
paramLabel = "ERROR_CODE_CLASSES",
|
||||||
|
description = ["Fully qualified class names of the error code classes to generate resources for"]
|
||||||
|
)
|
||||||
|
var classes: List<String> = mutableListOf()
|
||||||
|
|
||||||
|
@CommandLine.Option(
|
||||||
|
names = ["--locales"],
|
||||||
|
description = ["The set of locales to generate resource files for. Specified as locale tags, for example en-US"],
|
||||||
|
arity = "1"
|
||||||
|
)
|
||||||
|
var locales: List<String> = listOf("en-US")
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val logger = LoggerFactory.getLogger(ResourceGeneratorCLI::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun runProgram(): Int {
|
||||||
|
val jarFileLocation = ErrorToolCLIUtilities.checkDirectory(jarFile, "error code definition class files")
|
||||||
|
val resourceLocation = ErrorToolCLIUtilities.checkDirectory(resourceDir, "resource bundle files")
|
||||||
|
val resourceGenerator = ResourceGenerator(locales.map { Locale.forLanguageTag(it) })
|
||||||
|
try {
|
||||||
|
val resources = ErrorResourceUtilities.listResourceFiles(resourceLocation)
|
||||||
|
val loader = ErrorResourceUtilities.loaderFromDirectory(jarFileLocation)
|
||||||
|
val missingResources = resourceGenerator.calculateMissingResources(classes, resources, loader)
|
||||||
|
resourceGenerator.createResources(missingResources, resourceLocation)
|
||||||
|
loader.close()
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
logger.error(e.message, e)
|
||||||
|
return ExitCodes.FAILURE
|
||||||
|
}
|
||||||
|
return ExitCodes.SUCCESS
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,15 @@
|
|||||||
|
package net.corda.errorUtilities.docsTable
|
||||||
|
|
||||||
import junit.framework.TestCase.assertEquals
|
import junit.framework.TestCase.assertEquals
|
||||||
import net.corda.errorPageBuilder.ErrorTableGenerator
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.io.File
|
|
||||||
import java.lang.IllegalArgumentException
|
import java.lang.IllegalArgumentException
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class ErrorTableGeneratorTest {
|
class DocsTableGeneratorTest {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val RESOURCE_LOCATION = Paths.get("src/test/resources/test-errors").toAbsolutePath().toFile()
|
private val RESOURCE_LOCATION = Paths.get("src/test/resources/test-errors").toAbsolutePath()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val englishTable = """| Error Code | Aliases | Description | Actions to Fix |
|
private val englishTable = """| Error Code | Aliases | Description | Actions to Fix |
|
||||||
@ -22,24 +22,24 @@ class ErrorTableGeneratorTest {
|
|||||||
/| test-error | foo, bar | Teachtaireacht tástála | Roinnt gníomhartha |
|
/| test-error | foo, bar | Teachtaireacht tástála | Roinnt gníomhartha |
|
||||||
""".trimMargin("/")
|
""".trimMargin("/")
|
||||||
|
|
||||||
@Test(timeout = 300_000)
|
@Test(timeout = 1000)
|
||||||
fun `check error table is produced as expected`() {
|
fun `check error table is produced as expected`() {
|
||||||
val generator = ErrorTableGenerator(RESOURCE_LOCATION, Locale.forLanguageTag("en-US"))
|
val generator = DocsTableGenerator(RESOURCE_LOCATION, Locale.forLanguageTag("en-US"))
|
||||||
val table = generator.generateMarkdown()
|
val table = generator.generateMarkdown()
|
||||||
// Raw strings in Kotlin always use Unix line endings, so this is required to keep the test passing on Windows
|
// Raw strings in Kotlin always use Unix line endings, so this is required to keep the test passing on Windows
|
||||||
assertEquals(englishTable.split("\n").joinToString(System.lineSeparator()), table)
|
assertEquals(englishTable.split("\n").joinToString(System.lineSeparator()), table)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(timeout = 300_000)
|
@Test(timeout = 1000)
|
||||||
fun `check table in other locales is produced as expected`() {
|
fun `check table in other locales is produced as expected`() {
|
||||||
val generator = ErrorTableGenerator(RESOURCE_LOCATION, Locale.forLanguageTag("ga-IE"))
|
val generator = DocsTableGenerator(RESOURCE_LOCATION, Locale.forLanguageTag("ga-IE"))
|
||||||
val table = generator.generateMarkdown()
|
val table = generator.generateMarkdown()
|
||||||
assertEquals(irishTable.split("\n").joinToString(System.lineSeparator()), table)
|
assertEquals(irishTable.split("\n").joinToString(System.lineSeparator()), table)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException::class, timeout = 300_000)
|
@Test(expected = IllegalArgumentException::class, timeout = 1000)
|
||||||
fun `error thrown if unknown directory passed to generator`() {
|
fun `error thrown if unknown directory passed to generator`() {
|
||||||
val generator = ErrorTableGenerator(File("not/a/directory"), Locale.getDefault())
|
val generator = DocsTableGenerator(Paths.get("not/a/directory"), Locale.getDefault())
|
||||||
generator.generateMarkdown()
|
generator.generateMarkdown()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
package net.corda.errorUtilities.resourceGenerator
|
||||||
|
|
||||||
|
import junit.framework.TestCase.assertEquals
|
||||||
|
import net.corda.common.logging.errorReporting.ResourceBundleProperties
|
||||||
|
import org.junit.Test
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class ResourceGeneratorTest {
|
||||||
|
|
||||||
|
private val classes = listOf(TestCodes1::class.qualifiedName!!, TestCodes2::class.qualifiedName!!)
|
||||||
|
|
||||||
|
private fun expectedCodes() : List<String> {
|
||||||
|
val codes1 = TestCodes1.values().map { "${it.namespace.toLowerCase()}-${it.name.replace("_", "-").toLowerCase()}" }
|
||||||
|
val codes2 = TestCodes2.values().map { "${it.namespace.toLowerCase()}-${it.name.replace("_", "-").toLowerCase()}" }
|
||||||
|
return codes1 + codes2
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 1000)
|
||||||
|
fun `no codes marked as missing if all resources are present`() {
|
||||||
|
val resourceGenerator = ResourceGenerator(listOf())
|
||||||
|
val currentFiles = expectedCodes().map { "$it.properties" }
|
||||||
|
val missing = resourceGenerator.calculateMissingResources(classes, currentFiles, TestCodes1::class.java.classLoader)
|
||||||
|
assertEquals(setOf<String>(), missing.toSet())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 1000)
|
||||||
|
fun `missing locales are marked as missing when other locales are present`() {
|
||||||
|
val resourceGenerator = ResourceGenerator(listOf("en-US", "ga-IE").map { Locale.forLanguageTag(it) })
|
||||||
|
val currentFiles = expectedCodes().flatMap { listOf("$it.properties", "${it}_en_US.properties") }
|
||||||
|
val missing = resourceGenerator.calculateMissingResources(classes, currentFiles, TestCodes1::class.java.classLoader)
|
||||||
|
assertEquals(expectedCodes().map { "${it}_ga_IE.properties" }.toSet(), missing.toSet())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 1000)
|
||||||
|
fun `test writing out files works correctly`() {
|
||||||
|
// First test that if all files are missing then the resource generator detects this
|
||||||
|
val resourceGenerator = ResourceGenerator(listOf())
|
||||||
|
val currentFiles = listOf<String>()
|
||||||
|
val missing = resourceGenerator.calculateMissingResources(classes, currentFiles, TestCodes1::class.java.classLoader)
|
||||||
|
assertEquals(expectedCodes().map { "$it.properties" }.toSet(), missing.toSet())
|
||||||
|
|
||||||
|
// Now check that all resource files that should be created are
|
||||||
|
val tempDir = createTempDir()
|
||||||
|
resourceGenerator.createResources(missing, tempDir.toPath())
|
||||||
|
val createdFiles = tempDir.walkTopDown().filter { it.isFile && it.extension == "properties" }.map { it.name }.toList()
|
||||||
|
assertEquals(missing, createdFiles)
|
||||||
|
|
||||||
|
// Now check that a created file has the expected properties and values
|
||||||
|
val properties = Properties()
|
||||||
|
properties.load(tempDir.walk().filter { it.isFile && it.extension == "properties"}.first().inputStream())
|
||||||
|
assertEquals(ResourceGenerator.SHORT_DESCRIPTION_DEFAULT, properties.getProperty(ResourceBundleProperties.SHORT_DESCRIPTION))
|
||||||
|
assertEquals(ResourceGenerator.ACTIONS_TO_FIX_DEFAULT, properties.getProperty(ResourceBundleProperties.ACTIONS_TO_FIX))
|
||||||
|
assertEquals(ResourceGenerator.MESSAGE_TEMPLATE_DEFAULT, properties.getProperty(ResourceBundleProperties.MESSAGE_TEMPLATE))
|
||||||
|
assertEquals(ResourceGenerator.ALIASES_DEFAULT, properties.getProperty(ResourceBundleProperties.ALIASES))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package net.corda.errorUtilities.resourceGenerator
|
||||||
|
|
||||||
|
import net.corda.common.logging.errorReporting.ErrorCodes
|
||||||
|
|
||||||
|
// These test errors are not used directly, but their compiled class files are used to verify the resource generator functionality.
|
||||||
|
enum class TestNamespaces {
|
||||||
|
TN1,
|
||||||
|
TN2
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class TestCodes1 : ErrorCodes {
|
||||||
|
CASE1,
|
||||||
|
CASE2;
|
||||||
|
|
||||||
|
override val namespace = TestNamespaces.TN1.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class TestCodes2 : ErrorCodes {
|
||||||
|
CASE1,
|
||||||
|
CASE3;
|
||||||
|
|
||||||
|
override val namespace = TestNamespaces.TN2.toString()
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user