mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
[EG-438] Error Reporting Framework (#6125)
* [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] Add kdocs for error resource static methods * [EG-438] Handle enums defined with underscores * [EG-438] Slight refactoring of startup code * [EG-438] Port changes to error reporting code from future branch * [EG-438] Also port test changes * [EG-438] Suppress a deliberately unused parameter
This commit is contained in:
parent
3b335ebb00
commit
ab43238420
@ -16,6 +16,8 @@ dependencies {
|
||||
compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version"
|
||||
|
||||
testCompile project(":test-utils")
|
||||
testCompile "com.nhaarman:mockito-kotlin:$mockito_kotlin_version"
|
||||
testCompile "org.mockito:mockito-core:$mockito_version"
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,31 @@
|
||||
package net.corda.common.logging.errorReporting
|
||||
|
||||
import net.corda.common.logging.CordaVersion
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Provides information specific to Corda to the error reporting library.
|
||||
*
|
||||
* The primary use of this is to provide the URL to the docs site where the error information is hosted.
|
||||
*/
|
||||
class CordaErrorContextProvider : ErrorContextProvider {
|
||||
|
||||
companion object {
|
||||
private const val BASE_URL = "https://docs.corda.net/docs"
|
||||
private const val OS_PAGES = "corda-os"
|
||||
private const val ENTERPRISE_PAGES = "corda-enterprise"
|
||||
private const val ERROR_CODE_PAGE = "error-codes.html"
|
||||
}
|
||||
|
||||
override fun getURL(locale: Locale): String {
|
||||
val versionNumber = CordaVersion.releaseVersion
|
||||
|
||||
// This slightly strange block here allows the code to be merged across to Enterprise with no changes.
|
||||
val productVersion = if (CordaVersion.platformEditionCode == "OS") {
|
||||
OS_PAGES
|
||||
} else {
|
||||
ENTERPRISE_PAGES
|
||||
}
|
||||
return "$BASE_URL/$productVersion/$versionNumber/$ERROR_CODE_PAGE"
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package net.corda.common.logging.errorReporting
|
||||
|
||||
/**
|
||||
* A type representing an error condition.
|
||||
*
|
||||
* Error codes should be used in situations where an error is expected and information can be provided back to the user about what they've
|
||||
* done wrong. Each error code should have a resource bundle defined for it, which contains set of properties that define the error string
|
||||
* in different languages. See the resource bundles in common/logging/src/main/resources/errorReporting for more details.
|
||||
*/
|
||||
interface ErrorCode<CODES> where CODES: ErrorCodes, CODES: Enum<CODES> {
|
||||
|
||||
/**
|
||||
* The error code.
|
||||
*
|
||||
* Error codes are used to indicate what sort of error occurred. A unique code should be returned for each possible
|
||||
* error condition that could be reported within the defined namespace. The code should very briefly describe what has gone wrong, e.g.
|
||||
* "failed-to-store" or "connection-unavailable".
|
||||
*/
|
||||
val code: CODES
|
||||
|
||||
/**
|
||||
* Parameters to pass to the string template when reporting this error. The corresponding template that defines the error string in the
|
||||
* resource bundle must be expecting this list of parameters. Parameters should be in the order required by the message template - for
|
||||
* example, if the message template is "This error has argument {0} and argument {1}", the first element of this list will be placed
|
||||
* into {0}.
|
||||
*/
|
||||
val parameters: List<Any>
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package net.corda.common.logging.errorReporting
|
||||
|
||||
/**
|
||||
* A collection of error codes.
|
||||
*
|
||||
* Types implementing this are required to be enum classes to be used in an error.
|
||||
*/
|
||||
interface ErrorCodes {
|
||||
/**
|
||||
* The namespace of this collection of errors.
|
||||
*
|
||||
* These are used to partition errors into categories, e.g. "database" or "cordapp". Namespaces should be unique, which can be enforced
|
||||
* by using enum elements.
|
||||
*/
|
||||
val namespace: String
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package net.corda.common.logging.errorReporting
|
||||
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Provide context around reported errors by supplying product specific information.
|
||||
*/
|
||||
interface ErrorContextProvider {
|
||||
|
||||
/**
|
||||
* Get the URL to the docs site where the error codes are hosted.
|
||||
*
|
||||
* Note that the correct docs site link is likely to depend on the following:
|
||||
* - The locale of the error message
|
||||
* - The product the error was reported from
|
||||
* - The version of the product the error was reported from
|
||||
*
|
||||
* The returned URL must be the link the to the error code table in the documentation.
|
||||
*
|
||||
* @param locale The locale of the link
|
||||
* @return The URL of the docs site, to be printed in the logs
|
||||
*/
|
||||
fun getURL(locale: Locale) : String
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package net.corda.common.logging.errorReporting
|
||||
|
||||
import org.slf4j.Logger
|
||||
|
||||
/**
|
||||
* Reports error conditions to the logs, using localised error messages.
|
||||
*/
|
||||
internal interface ErrorReporter {
|
||||
/**
|
||||
* Report a particular error condition
|
||||
*
|
||||
* @param error The error to report
|
||||
* @param logger The logger to use when reporting this error
|
||||
*/
|
||||
fun report(error: ErrorCode<*>, logger: Logger)
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package net.corda.common.logging.errorReporting
|
||||
|
||||
import org.slf4j.Logger
|
||||
import java.text.MessageFormat
|
||||
import java.util.*
|
||||
|
||||
internal const val ERROR_INFO_RESOURCE = "ErrorInfo"
|
||||
internal const val ERROR_CODE_MESSAGE = "errorCodeMessage"
|
||||
internal const val ERROR_CODE_URL = "errorCodeUrl"
|
||||
|
||||
internal class ErrorReporterImpl(private val resourceLocation: String,
|
||||
private val locale: Locale,
|
||||
private val errorContextProvider: ErrorContextProvider) : ErrorReporter {
|
||||
|
||||
private fun fetchAndFormat(resource: String, property: String, params: Array<out Any>) : String {
|
||||
val bundle = ResourceBundle.getBundle(resource, locale)
|
||||
val template = bundle.getString(property)
|
||||
val formatter = MessageFormat(template, locale)
|
||||
return formatter.format(params)
|
||||
}
|
||||
|
||||
// Returns the string appended to all reported errors, indicating the error code and the URL to go to.
|
||||
// e.g. [Code: my-error-code, For further information, please go to https://docs.corda.net/corda-os/4.5/error-codes.html]
|
||||
private fun getErrorInfo(error: ErrorCode<*>) : String {
|
||||
val resource = "$resourceLocation/$ERROR_INFO_RESOURCE"
|
||||
val codeMessage = fetchAndFormat(resource, ERROR_CODE_MESSAGE, arrayOf(error.formatCode()))
|
||||
val urlMessage = fetchAndFormat(resource, ERROR_CODE_URL, arrayOf(errorContextProvider.getURL(locale)))
|
||||
return "[$codeMessage, $urlMessage]"
|
||||
}
|
||||
|
||||
override fun report(error: ErrorCode<*>, logger: Logger) {
|
||||
val errorResource = ErrorResource.fromErrorCode(error, resourceLocation, locale)
|
||||
val message = "${errorResource.getErrorMessage(error.parameters.toTypedArray())} ${getErrorInfo(error)}"
|
||||
logger.error(message)
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package net.corda.common.logging.errorReporting
|
||||
|
||||
import java.lang.UnsupportedOperationException
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Entry point into the Error Reporting framework.
|
||||
*
|
||||
* This creates the error reporter used to report errors. The `initialiseReporting` method should be called to build a reporter before any
|
||||
* errors are reported.
|
||||
*/
|
||||
class ErrorReporting private constructor(private val localeString: String,
|
||||
private val resourceLocation: String,
|
||||
private val contextProvider: ErrorContextProvider?) {
|
||||
|
||||
constructor() : this(DEFAULT_LOCALE, DEFAULT_LOCATION, null)
|
||||
|
||||
private companion object {
|
||||
private const val DEFAULT_LOCALE = "en-US"
|
||||
private const val DEFAULT_LOCATION = "."
|
||||
|
||||
private var errorReporter: ErrorReporter? = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the locale to use when reporting errors
|
||||
*
|
||||
* @param locale The locale tag to use when reporting errors, e.g. en-US
|
||||
*/
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun withLocale(locale: String) : ErrorReporting {
|
||||
// Currently, if anything other than the default is used this is entirely untested. As a result, an exception is thrown here to
|
||||
// indicate that this functionality is not ready at this point in time.
|
||||
throw LocaleSettingUnsupportedException()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the location of the resource bundles containing the error codes.
|
||||
*
|
||||
* @param location The location within the JAR of the resource bundle
|
||||
*/
|
||||
fun usingResourcesAt(location: String) : ErrorReporting {
|
||||
return ErrorReporting(localeString, location, contextProvider)
|
||||
}
|
||||
|
||||
fun withContextProvider(contextProvider: ErrorContextProvider) : ErrorReporting {
|
||||
return ErrorReporting(localeString, resourceLocation, contextProvider)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the reporting of errors.
|
||||
*/
|
||||
fun initialiseReporting() {
|
||||
if (contextProvider == null) {
|
||||
throw NoContextProviderSuppliedException()
|
||||
}
|
||||
if (errorReporter != null) {
|
||||
throw DoubleInitializationException()
|
||||
}
|
||||
errorReporter = ErrorReporterImpl(resourceLocation, Locale.forLanguageTag(localeString), contextProvider)
|
||||
}
|
||||
|
||||
internal fun getReporter() : ErrorReporter {
|
||||
return errorReporter ?: throw ReportingUninitializedException()
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package net.corda.common.logging.errorReporting
|
||||
|
||||
abstract class ErrorReportingException(message: String, cause: Throwable? = null) : Exception(message, cause)
|
||||
|
||||
/**
|
||||
* Occurs when reporting is requested before the error reporting code has been initialized
|
||||
*/
|
||||
class ReportingUninitializedException : ErrorReportingException("Error reporting is uninitialized")
|
||||
|
||||
/**
|
||||
* Occurs when no error context provider is supplied while initializing error reporting
|
||||
*/
|
||||
class NoContextProviderSuppliedException
|
||||
: ErrorReportingException("No error context provider was supplied when initializing error reporting")
|
||||
|
||||
/**
|
||||
* Occurs if the error reporting framework has been initialized twice
|
||||
*/
|
||||
class DoubleInitializationException : ErrorReportingException("Error reporting has previously been initialized")
|
||||
|
||||
/**
|
||||
* Occurs if a locale is set while initializing the error reporting framework.
|
||||
*
|
||||
* This is done as locale support has not yet been properly designed, and so using anything other than the default is untested.
|
||||
*/
|
||||
class LocaleSettingUnsupportedException :
|
||||
ErrorReportingException("Setting a locale other than the default is not supported in the first release")
|
@ -0,0 +1,18 @@
|
||||
package net.corda.common.logging.errorReporting
|
||||
|
||||
import org.slf4j.Logger
|
||||
|
||||
/**
|
||||
* Report errors that have occurred.
|
||||
*
|
||||
* Doing this allows the error reporting framework to find the corresponding resources for the error and pick the correct locale.
|
||||
*
|
||||
* @param error The error that has occurred.
|
||||
*/
|
||||
fun Logger.report(error: ErrorCode<*>) = ErrorReporting().getReporter().report(error, this)
|
||||
|
||||
internal fun ErrorCode<*>.formatCode() : String {
|
||||
val namespaceString = this.code.namespace.toLowerCase().replace("_", "-")
|
||||
val codeString = this.code.toString().toLowerCase().replace("_", "-")
|
||||
return "$namespaceString-$codeString"
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package net.corda.common.logging.errorReporting
|
||||
|
||||
import net.corda.common.logging.errorReporting.ResourceBundleProperties.ACTIONS_TO_FIX
|
||||
import net.corda.common.logging.errorReporting.ResourceBundleProperties.ALIASES
|
||||
import net.corda.common.logging.errorReporting.ResourceBundleProperties.MESSAGE_TEMPLATE
|
||||
import net.corda.common.logging.errorReporting.ResourceBundleProperties.SHORT_DESCRIPTION
|
||||
import java.text.MessageFormat
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* A representation of a single error resource file.
|
||||
*
|
||||
* This handles selecting the right properties from the resource bundle and formatting the error message.
|
||||
*/
|
||||
class ErrorResource private constructor(private val bundle: ResourceBundle,
|
||||
private val locale: Locale) {
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Construct an error resource from a provided code.
|
||||
*
|
||||
* @param errorCode The code to get the resource bundle for
|
||||
* @param resourceLocation The location in the JAR of the error code resource bundles
|
||||
* @param locale The locale to use for this resource
|
||||
*/
|
||||
fun fromErrorCode(errorCode: ErrorCode<*>, resourceLocation: String, locale: Locale) : ErrorResource {
|
||||
val resource = "$resourceLocation/${errorCode.formatCode()}"
|
||||
val bundle = ResourceBundle.getBundle(resource, locale)
|
||||
return ErrorResource(bundle, locale)
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an error resource using resources loaded in a given classloader
|
||||
*
|
||||
* @param resource The resource bundle to load
|
||||
* @param classLoader The classloader used to load the resource bundles
|
||||
* @param locale The locale to use for this resource
|
||||
*/
|
||||
fun fromLoader(resource: String, classLoader: ClassLoader, locale: Locale) : ErrorResource {
|
||||
val bundle = ResourceBundle.getBundle(resource, locale, classLoader)
|
||||
return ErrorResource(bundle, locale)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getProperty(propertyName: String) : String = bundle.getString(propertyName)
|
||||
|
||||
private fun formatTemplate(template: String, args: Array<Any>) : String {
|
||||
val formatter = MessageFormat(template, locale)
|
||||
return formatter.format(args)
|
||||
}
|
||||
|
||||
fun getErrorMessage(args: Array<Any>): String {
|
||||
val template = getProperty(MESSAGE_TEMPLATE)
|
||||
return formatTemplate(template, args)
|
||||
}
|
||||
|
||||
val shortDescription: String = getProperty(SHORT_DESCRIPTION)
|
||||
val actionsToFix: String = getProperty(ACTIONS_TO_FIX)
|
||||
val aliases: String = getProperty(ALIASES)
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package net.corda.common.logging.errorReporting
|
||||
|
||||
/**
|
||||
* Namespaces for errors within the node.
|
||||
*/
|
||||
enum class NodeNamespaces {
|
||||
DATABASE
|
||||
}
|
||||
|
||||
/**
|
||||
* Errors related to database connectivity
|
||||
*/
|
||||
enum class NodeDatabaseErrors : ErrorCodes {
|
||||
COULD_NOT_CONNECT,
|
||||
MISSING_DRIVER,
|
||||
FAILED_STARTUP,
|
||||
PASSWORD_REQUIRED_FOR_H2;
|
||||
|
||||
override val namespace = NodeNamespaces.DATABASE.toString()
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package net.corda.common.logging.errorReporting
|
||||
|
||||
/**
|
||||
* Constants defining the properties available in the resource bundle files.
|
||||
*/
|
||||
object ResourceBundleProperties {
|
||||
const val MESSAGE_TEMPLATE = "errorTemplate"
|
||||
const val SHORT_DESCRIPTION = "shortDescription"
|
||||
const val ACTIONS_TO_FIX = "actionsToFix"
|
||||
const val ALIASES = "aliases"
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
errorCodeMessage = Error Code: {0}
|
||||
errorCodeUrl = For further information, please go to {0}
|
@ -0,0 +1,2 @@
|
||||
errorCodeMessage = Error Code: {0}
|
||||
errorCodeUrl = For further information, please go to {0}
|
@ -0,0 +1,4 @@
|
||||
errorTemplate = Could not connect to the database. Please check your JDBC connection URL, or the connectivity to the database.
|
||||
shortDescription = The node failed to connect to the database on node startup, preventing the node from starting correctly.
|
||||
actionsToFix = This happens either because the database connection has been misconfigured or the database is unreachable. Check that the JDBC URL is configured correctly in your node.conf. If this is correctly configured, then check your database connection.
|
||||
aliases =
|
@ -0,0 +1,3 @@
|
||||
errorTemplate = Could not connect to the database. Please check your JDBC connection URL, or the connectivity to the database.
|
||||
shortDescription = The node failed to connect to the database on node startup, preventing the node from starting correctly.
|
||||
actionsToFix = This happens either because the database connection has been misconfigured or the database is unreachable. Check that the JDBC URL is configured correctly in your node.conf. If this is correctly configured, then check your database connection.
|
@ -0,0 +1,4 @@
|
||||
errorTemplate = Failed to create the datasource. See the logs for further information and the cause.
|
||||
shortDescription = The datasource could not be created for unknown reasons.
|
||||
actionsToFix = The logs in the logs directory should contain more information on what went wrong.
|
||||
aliases =
|
@ -0,0 +1,3 @@
|
||||
errorTemplate = Failed to create the datasource. See the logs for further information and the cause.
|
||||
shortDescription = The datasource could not be created for unknown reasons.
|
||||
actionsToFix = The logs in the logs directory should contain more information on what went wrong.
|
@ -0,0 +1,4 @@
|
||||
errorTemplate = Could not find the database driver class. Please add it to the 'drivers' folder.
|
||||
shortDescription = The node could not find the driver in the 'drivers' directory.
|
||||
actionsToFix = Please ensure that the correct database driver has been placed in the 'drivers' folder. The driver must contain the driver main class specified in 'node.conf'.
|
||||
aliases =
|
@ -0,0 +1,3 @@
|
||||
errorTemplate = Could not find the database driver class. Please add it to the 'drivers' folder.
|
||||
shortDescription = The node could not find the driver in the 'drivers' directory.
|
||||
actionsToFix = Please ensure that the correct database driver has been placed in the 'drivers' folder. The driver must contain the driver main class specified in 'node.conf'.
|
@ -0,0 +1,4 @@
|
||||
errorTemplate = Database password is required for H2 server listening on {0}
|
||||
shortDescription = A password is required to access the H2 server the node is trying to access, and this password is missing.
|
||||
actionsToFix = Add the required password to the 'datasource.password' configuration in 'node.conf'.
|
||||
aliases =
|
@ -0,0 +1,3 @@
|
||||
errorTemplate = Database password is required for H2 server listening on {0}
|
||||
shortDescription = A password is required to access the H2 server the node is trying to access, and this password is missing.
|
||||
actionsToFix = Add the required password to the 'datasource.password' configuration in 'node.conf'.
|
@ -0,0 +1,20 @@
|
||||
package net.corda.commmon.logging.errorReporting
|
||||
|
||||
import net.corda.common.logging.CordaVersion
|
||||
import net.corda.common.logging.errorReporting.CordaErrorContextProvider
|
||||
import org.junit.Test
|
||||
import java.util.*
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class CordaErrorContextProviderTest {
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `check that correct URL is returned from context provider`() {
|
||||
val context = CordaErrorContextProvider()
|
||||
val expectedURL = "https://docs.corda.net/docs/corda-os/${CordaVersion.releaseVersion}/error-codes.html"
|
||||
// In this first release, there is only one localisation and the URL structure for future localisations is currently unknown. As
|
||||
// a result, the same URL is expected for all locales.
|
||||
assertEquals(expectedURL, context.getURL(Locale.getDefault()))
|
||||
assertEquals(expectedURL, context.getURL(Locale.forLanguageTag("es-ES")))
|
||||
}
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
package net.corda.commmon.logging.errorReporting
|
||||
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import net.corda.common.logging.errorReporting.ErrorCode
|
||||
import net.corda.common.logging.errorReporting.ErrorCodes
|
||||
import net.corda.common.logging.errorReporting.ErrorContextProvider
|
||||
import net.corda.common.logging.errorReporting.ErrorReporterImpl
|
||||
import org.junit.After
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TestName
|
||||
import org.mockito.ArgumentMatchers.anyString
|
||||
import org.mockito.Mockito
|
||||
import org.slf4j.Logger
|
||||
import java.text.DateFormat
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
|
||||
class ErrorReporterImplTest {
|
||||
|
||||
companion object {
|
||||
private const val TEST_URL = "test-url"
|
||||
}
|
||||
|
||||
private val logs: MutableList<Any> = mutableListOf()
|
||||
|
||||
private val loggerMock = Mockito.mock(Logger::class.java).also {
|
||||
Mockito.`when`(it.error(anyString())).then { logs.addAll(it.arguments) }
|
||||
}
|
||||
|
||||
private val contextProvider: ErrorContextProvider = object : ErrorContextProvider {
|
||||
override fun getURL(locale: Locale): String {
|
||||
return "$TEST_URL/${locale.toLanguageTag()}"
|
||||
}
|
||||
}
|
||||
|
||||
private enum class TestNamespaces {
|
||||
TEST
|
||||
}
|
||||
|
||||
private enum class TestErrors : ErrorCodes {
|
||||
CASE1,
|
||||
CASE2,
|
||||
CASE_3;
|
||||
|
||||
override val namespace = TestNamespaces.TEST.toString()
|
||||
}
|
||||
|
||||
private val TEST_ERROR_1 = object : ErrorCode<TestErrors> {
|
||||
override val code = TestErrors.CASE1
|
||||
override val parameters = listOf<Any>()
|
||||
}
|
||||
|
||||
private class TestError2(currentDate: Date) : ErrorCode<TestErrors> {
|
||||
override val code = TestErrors.CASE2
|
||||
override val parameters: List<Any> = listOf("foo", 1, currentDate)
|
||||
}
|
||||
|
||||
private val TEST_ERROR_3 = object : ErrorCode<TestErrors> {
|
||||
override val code = TestErrors.CASE_3
|
||||
override val parameters = listOf<Any>()
|
||||
}
|
||||
|
||||
private fun createReporterImpl(localeTag: String?) : ErrorReporterImpl {
|
||||
val locale = if (localeTag != null) Locale.forLanguageTag(localeTag) else Locale.getDefault()
|
||||
return ErrorReporterImpl("errorReporting", locale, contextProvider)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
logs.clear()
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `error codes logged correctly`() {
|
||||
val error = TEST_ERROR_1
|
||||
val testReporter = createReporterImpl("en-US")
|
||||
testReporter.report(error, loggerMock)
|
||||
assertEquals(listOf("This is a test message [Code: test-case1, URL: $TEST_URL/en-US]"), logs)
|
||||
}
|
||||
|
||||
@Test(timeout = 300_00)
|
||||
fun `error code with parameters correctly reported`() {
|
||||
val currentDate = Date.from(Instant.now())
|
||||
val error = TestError2(currentDate)
|
||||
val testReporter = createReporterImpl("en-US")
|
||||
testReporter.report(error, loggerMock)
|
||||
val format = DateFormat.getDateInstance(DateFormat.LONG, Locale.forLanguageTag("en-US"))
|
||||
assertEquals(listOf("This is the second case with string foo, number 1, date ${format.format(currentDate)} [Code: test-case2, URL: $TEST_URL/en-US]"), logs)
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `locale used with no corresponding resource falls back to default`() {
|
||||
val error = TEST_ERROR_1
|
||||
val testReporter = createReporterImpl("fr-FR")
|
||||
testReporter.report(error, loggerMock)
|
||||
assertEquals(listOf("This is a test message [Code: test-case1, URL: $TEST_URL/fr-FR]"), logs)
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `locale with corresponding resource causes correct error to be printed`() {
|
||||
val error = TEST_ERROR_1
|
||||
val testReporter = createReporterImpl("ga-IE")
|
||||
testReporter.report(error, loggerMock)
|
||||
assertEquals(listOf("Is teachtaireacht earráide é seo [Code: test-case1, URL: $TEST_URL/ga-IE]"), logs)
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `locale with missing properties falls back to default properties`() {
|
||||
val error = TEST_ERROR_1
|
||||
val testReporter = createReporterImpl("es-ES")
|
||||
testReporter.report(error, loggerMock)
|
||||
assertEquals(listOf("This is a test message [Code: test-case1, URL: $TEST_URL/es-ES]"), logs)
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `error code with underscore in translated to resource file successfully`() {
|
||||
val error = TEST_ERROR_3
|
||||
val testReporter = createReporterImpl("en-US")
|
||||
testReporter.report(error, loggerMock)
|
||||
assertEquals(listOf("This is the third test message [Code: test-case-3, URL: $TEST_URL/en-US]"), logs)
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
errorCodeMessage = Code: {0}
|
||||
errorCodeUrl = URL: {0}
|
@ -0,0 +1,2 @@
|
||||
errorCodeMessage = Code: {0}
|
||||
errorCodeUrl = URL: {0}
|
@ -0,0 +1,4 @@
|
||||
errorTemplate = This is the third test message
|
||||
shortDescription = Test description
|
||||
actionsToFix = Actions
|
||||
aliases =
|
@ -0,0 +1,4 @@
|
||||
errorTemplate = This is the third test message
|
||||
shortDescription = Test description
|
||||
actionsToFix = Actions
|
||||
aliases =
|
@ -0,0 +1,4 @@
|
||||
errorTemplate = This is a test message
|
||||
shortDescription = Test description
|
||||
actionsToFix = Actions
|
||||
aliases = foo, bar
|
@ -0,0 +1,3 @@
|
||||
errorTemplate = This is a test message
|
||||
shortDescription = Test description
|
||||
actionsToFix = Actions
|
@ -0,0 +1 @@
|
||||
shortDescription = Descripción de la prueba
|
@ -0,0 +1,3 @@
|
||||
errorTemplate = Is teachtaireacht earráide é seo
|
||||
shortDescription = Teachtaireacht tástála
|
||||
actionsToFix = Roinnt gníomhartha
|
@ -0,0 +1,4 @@
|
||||
errorTemplate = This is the second case with string {0}, number {1, number, integer}, date {2, date, long}
|
||||
shortDescription = Test description
|
||||
actionsToFix = Actions
|
||||
aliases = ""
|
@ -0,0 +1,4 @@
|
||||
errorTemplate = This is the second case with string {0}, number {1, number, integer}, date {2, date, long}
|
||||
shortDescription = Test description
|
||||
actionsToFix = Actions
|
||||
aliases =
|
@ -0,0 +1,4 @@
|
||||
errorTemplate = Seo an dara cás le sreang {0}, uimhir {1, uimhir, slánuimhir}, dáta {2, dáta, fada}
|
||||
shortDescription = An dara tuairisc
|
||||
actionsToFix = Roinnt gníomhartha eile
|
||||
aliases =
|
@ -9,6 +9,7 @@ dependencies {
|
||||
compile project(":core")
|
||||
compile project(":serialization") // TODO Remove this once the NetworkBootstrapper class is moved into the tools:bootstrapper module
|
||||
compile project(':common-configuration-parsing') // TODO Remove this dependency once NetworkBootsrapper is moved into tools:bootstrapper
|
||||
compile project(':common-logging')
|
||||
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
|
@ -4,10 +4,13 @@ import co.paralleluniverse.strands.Strand
|
||||
import com.zaxxer.hikari.HikariDataSource
|
||||
import com.zaxxer.hikari.pool.HikariPool
|
||||
import com.zaxxer.hikari.util.ConcurrentBag
|
||||
import net.corda.common.logging.errorReporting.ErrorCode
|
||||
import net.corda.core.flows.HospitalizeFlowException
|
||||
import net.corda.core.internal.NamedCacheFactory
|
||||
import net.corda.core.schemas.MappedSchema
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.common.logging.errorReporting.NodeDatabaseErrors
|
||||
import net.corda.common.logging.errorReporting.NodeNamespaces
|
||||
import org.hibernate.tool.schema.spi.SchemaManagementException
|
||||
import rx.Observable
|
||||
import rx.Subscriber
|
||||
@ -405,7 +408,10 @@ private fun Throwable.hasSQLExceptionCause(): Boolean =
|
||||
else -> cause?.hasSQLExceptionCause() ?: false
|
||||
}
|
||||
|
||||
class CouldNotCreateDataSourceException(override val message: String?, override val cause: Throwable? = null) : Exception()
|
||||
class CouldNotCreateDataSourceException(override val message: String?,
|
||||
override val code: NodeDatabaseErrors,
|
||||
override val parameters: List<Any> = listOf(),
|
||||
override val cause: Throwable? = null) : ErrorCode<NodeDatabaseErrors>, Exception()
|
||||
|
||||
class HibernateSchemaChangeException(override val message: String?, override val cause: Throwable? = null): Exception()
|
||||
|
||||
|
@ -152,6 +152,7 @@ import net.corda.nodeapi.internal.crypto.X509Utilities.DISTRIBUTED_NOTARY_KEY_AL
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities.NODE_IDENTITY_KEY_ALIAS
|
||||
import net.corda.node.utilities.cryptoservice.CryptoServiceFactory
|
||||
import net.corda.node.utilities.cryptoservice.SupportedCryptoServices
|
||||
import net.corda.common.logging.errorReporting.NodeDatabaseErrors
|
||||
import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService
|
||||
import net.corda.nodeapi.internal.lifecycle.NodeLifecycleEvent
|
||||
import net.corda.nodeapi.internal.lifecycle.NodeLifecycleEventsDistributor
|
||||
@ -1302,10 +1303,19 @@ fun CordaPersistence.startHikariPool(hikariProperties: Properties, databaseConfi
|
||||
start(dataSource)
|
||||
} catch (ex: Exception) {
|
||||
when {
|
||||
ex is HikariPool.PoolInitializationException -> throw CouldNotCreateDataSourceException("Could not connect to the database. Please check your JDBC connection URL, or the connectivity to the database.", ex)
|
||||
ex.cause is ClassNotFoundException -> throw CouldNotCreateDataSourceException("Could not find the database driver class. Please add it to the 'drivers' folder. See: https://docs.corda.net/corda-configuration-file.html")
|
||||
ex is HikariPool.PoolInitializationException -> throw CouldNotCreateDataSourceException(
|
||||
"Could not connect to the database. Please check your JDBC connection URL, or the connectivity to the database.",
|
||||
NodeDatabaseErrors.COULD_NOT_CONNECT,
|
||||
cause = ex)
|
||||
ex.cause is ClassNotFoundException -> throw CouldNotCreateDataSourceException(
|
||||
"Could not find the database driver class. Please add it to the 'drivers' folder. See: https://docs.corda.net/corda-configuration-file.html",
|
||||
NodeDatabaseErrors.MISSING_DRIVER)
|
||||
ex is OutstandingDatabaseChangesException -> throw (DatabaseIncompatibleException(ex.message))
|
||||
else -> throw CouldNotCreateDataSourceException("Could not create the DataSource: ${ex.message}", ex)
|
||||
else ->
|
||||
throw CouldNotCreateDataSourceException(
|
||||
"Could not create the DataSource: ${ex.message}",
|
||||
NodeDatabaseErrors.FAILED_STARTUP,
|
||||
cause = ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -73,6 +73,7 @@ import net.corda.node.utilities.DefaultNamedCacheFactory
|
||||
import net.corda.node.utilities.DemoClock
|
||||
import net.corda.node.utilities.errorAndTerminate
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingClient
|
||||
import net.corda.common.logging.errorReporting.NodeDatabaseErrors
|
||||
import net.corda.nodeapi.internal.ShutdownHook
|
||||
import net.corda.nodeapi.internal.addShutdownHook
|
||||
import net.corda.nodeapi.internal.bridging.BridgeControlListener
|
||||
@ -503,7 +504,10 @@ open class Node(configuration: NodeConfiguration,
|
||||
if (effectiveH2Settings?.address != null) {
|
||||
if (!InetAddress.getByName(effectiveH2Settings.address.host).isLoopbackAddress
|
||||
&& configuration.dataSourceProperties.getProperty("dataSource.password").isBlank()) {
|
||||
throw CouldNotCreateDataSourceException("Database password is required for H2 server listening on ${InetAddress.getByName(effectiveH2Settings.address.host)}.")
|
||||
throw CouldNotCreateDataSourceException(
|
||||
"Database password is required for H2 server listening on ${InetAddress.getByName(effectiveH2Settings.address.host)}.",
|
||||
NodeDatabaseErrors.PASSWORD_REQUIRED_FOR_H2,
|
||||
listOf(InetAddress.getByName(effectiveH2Settings.address.host).toString()))
|
||||
}
|
||||
val databaseName = databaseUrl.removePrefix(h2Prefix).substringBefore(';')
|
||||
val baseDir = Paths.get(databaseName).parent.toString()
|
||||
|
@ -6,6 +6,8 @@ import net.corda.cliutils.CordaCliWrapper
|
||||
import net.corda.cliutils.ExitCodes
|
||||
import net.corda.cliutils.printError
|
||||
import net.corda.common.logging.CordaVersion
|
||||
import net.corda.common.logging.errorReporting.CordaErrorContextProvider
|
||||
import net.corda.common.logging.errorReporting.ErrorCode
|
||||
import net.corda.core.contracts.HashAttachmentConstraint
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.internal.*
|
||||
@ -16,6 +18,8 @@ import net.corda.core.utilities.Try
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.node.*
|
||||
import net.corda.common.logging.errorReporting.ErrorReporting
|
||||
import net.corda.common.logging.errorReporting.report
|
||||
import net.corda.node.internal.Node.Companion.isInvalidJavaVersion
|
||||
import net.corda.node.internal.cordapp.MultipleCordappsForFlowException
|
||||
import net.corda.node.internal.subcommands.*
|
||||
@ -140,6 +144,7 @@ open class NodeStartup : NodeStartupLogging {
|
||||
private val logger by lazy { loggerFor<Node>() } // I guess this is lazy to allow for logging init, but why Node?
|
||||
const val LOGS_DIRECTORY_NAME = "logs"
|
||||
const val LOGS_CAN_BE_FOUND_IN_STRING = "Logs can be found in"
|
||||
const val ERROR_CODE_RESOURCE_LOCATION = "error-codes"
|
||||
}
|
||||
|
||||
lateinit var cmdLineOptions: SharedNodeCmdLineOptions
|
||||
@ -169,6 +174,10 @@ open class NodeStartup : NodeStartupLogging {
|
||||
?: return ExitCodes.FAILURE
|
||||
val configuration = cmdLineOptions.parseConfiguration(rawConfig).doIfValid { logRawConfig(rawConfig) }.doOnErrors(::logConfigurationErrors).optional
|
||||
?: return ExitCodes.FAILURE
|
||||
ErrorReporting()
|
||||
.usingResourcesAt(ERROR_CODE_RESOURCE_LOCATION)
|
||||
.withContextProvider(CordaErrorContextProvider())
|
||||
.initialiseReporting()
|
||||
|
||||
// Step 6. Check if we can access the certificates directory
|
||||
if (requireCertificates && !canReadCertificatesDirectory(configuration.certificatesDirectory, configuration.devMode)) return ExitCodes.FAILURE
|
||||
@ -482,6 +491,7 @@ interface NodeStartupLogging {
|
||||
|
||||
fun handleStartError(error: Throwable) {
|
||||
when {
|
||||
error is ErrorCode<*> -> logger.report(error)
|
||||
error.isExpectedWhenStartingNode() -> error.logAsExpected()
|
||||
error is CouldNotCreateDataSourceException -> error.logAsUnexpected()
|
||||
error is Errors.NativeIoException && error.message?.contains("Address already in use") == true -> error.logAsExpected("One of the ports required by the Corda node is already in use.")
|
||||
|
@ -108,4 +108,5 @@ include 'serialization-deterministic'
|
||||
|
||||
include 'tools:checkpoint-agent'
|
||||
include 'detekt-plugins'
|
||||
include 'tools:error-page-builder'
|
||||
|
||||
|
32
tools/error-page-builder/build.gradle
Normal file
32
tools/error-page-builder/build.gradle
Normal file
@ -0,0 +1,32 @@
|
||||
group 'net.corda'
|
||||
version '4.5-SNAPSHOT'
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(":common-logging")
|
||||
implementation project(":tools:cliutils")
|
||||
implementation "info.picocli:picocli:$picocli_version"
|
||||
testCompile group: 'junit', name: 'junit', version: '4.12'
|
||||
|
||||
implementation "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
|
||||
}
|
||||
|
||||
jar {
|
||||
enabled = false
|
||||
classifier = 'ignore'
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
baseName = "corda-tools-error-page-builder"
|
||||
manifest {
|
||||
attributes(
|
||||
'Main-Class': "net.corda.errorPageBuilder.ErrorPageBuilderKt"
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
package net.corda.errorPageBuilder
|
||||
|
||||
import net.corda.cliutils.CordaCliWrapper
|
||||
import net.corda.cliutils.ExitCodes
|
||||
import net.corda.cliutils.start
|
||||
import org.slf4j.LoggerFactory
|
||||
import picocli.CommandLine
|
||||
import java.io.File
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val builder = ErrorPageBuilder()
|
||||
builder.start(args)
|
||||
}
|
||||
|
||||
class ErrorPageBuilder : CordaCliWrapper("error-page-builder", "Builds the error table for the error codes page") {
|
||||
|
||||
@CommandLine.Parameters(
|
||||
index = "0",
|
||||
paramLabel = "OUTPUT_LOCATION",
|
||||
arity = "1",
|
||||
description = ["The file system location to output the error codes documentation table"]
|
||||
)
|
||||
var outputDir: Path? = null
|
||||
|
||||
@CommandLine.Parameters(
|
||||
index = "1",
|
||||
paramLabel = "RESOURCE_LOCATION",
|
||||
arity = "1",
|
||||
description = ["The file system location of the resource files to process"]
|
||||
)
|
||||
var resourceLocation: Path? = null
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ["--locale-tag"],
|
||||
description = ["The locale tag of the locale to use when localising the error codes table. For example, en-US"],
|
||||
arity = "1"
|
||||
)
|
||||
var localeTag: String? = null
|
||||
|
||||
companion object {
|
||||
private val logger = LoggerFactory.getLogger(ErrorPageBuilder::class.java)
|
||||
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 {
|
||||
val locale = if (localeTag != null) Locale.forLanguageTag(localeTag) else Locale.getDefault()
|
||||
val (outputFile, resources) = try {
|
||||
Pair(getOutputFile(), getResourceDir())
|
||||
} catch (e: IllegalArgumentException) {
|
||||
logger.error(e.message, e)
|
||||
return ExitCodes.FAILURE
|
||||
}
|
||||
val tableGenerator = ErrorTableGenerator(resources.toFile(), locale)
|
||||
try {
|
||||
val table = tableGenerator.generateMarkdown()
|
||||
outputFile.writeText(table)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
logger.error(e.message, e)
|
||||
return ExitCodes.FAILURE
|
||||
}
|
||||
return ExitCodes.SUCCESS
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package net.corda.errorPageBuilder
|
||||
|
||||
import net.corda.common.logging.errorReporting.ErrorResource
|
||||
import java.io.File
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.net.URLClassLoader
|
||||
import java.util.*
|
||||
|
||||
class ErrorTableGenerator(private val resourceLocation: File,
|
||||
private val locale: Locale) {
|
||||
|
||||
companion object {
|
||||
private const val ERROR_CODE_HEADING = "codeHeading"
|
||||
private const val ALIASES_HEADING = "aliasesHeading"
|
||||
private const val DESCRIPTION_HEADING = "descriptionHeading"
|
||||
private const val TO_FIX_HEADING = "toFixHeading"
|
||||
private const val ERROR_HEADINGS_BUNDLE = "ErrorPageHeadings"
|
||||
private const val ERROR_INFO_RESOURCE = "ErrorInfo.properties"
|
||||
}
|
||||
|
||||
private fun getHeading(heading: String) : String {
|
||||
val resource = ResourceBundle.getBundle(ERROR_HEADINGS_BUNDLE, locale)
|
||||
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>> {
|
||||
val table = mutableListOf<List<String>>()
|
||||
val loader = createLoader()
|
||||
for (resource in listResources()) {
|
||||
val errorResource = ErrorResource.fromLoader(resource, loader, locale)
|
||||
table.add(listOf(resource, errorResource.aliases, errorResource.shortDescription, errorResource.actionsToFix))
|
||||
}
|
||||
return table
|
||||
}
|
||||
|
||||
private fun formatTable(tableData: List<List<String>>) : String {
|
||||
val headings = listOf(
|
||||
getHeading(ERROR_CODE_HEADING),
|
||||
getHeading(ALIASES_HEADING),
|
||||
getHeading(DESCRIPTION_HEADING),
|
||||
getHeading(TO_FIX_HEADING)
|
||||
)
|
||||
val underlines = headings.map { "-".repeat(it.length) }
|
||||
val fullTable = listOf(headings, underlines) + tableData
|
||||
return fullTable.joinToString(System.lineSeparator()) { it.joinToString(prefix = "| ", postfix = " |", separator = " | ") }
|
||||
}
|
||||
|
||||
fun generateMarkdown() : String {
|
||||
if (!resourceLocation.exists()) throw IllegalArgumentException("Directory $resourceLocation does not exist.")
|
||||
val tableData = generateTable()
|
||||
return formatTable(tableData)
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
codeHeading = Error Code
|
||||
aliasesHeading = Aliases
|
||||
descriptionHeading = Description
|
||||
toFixHeading = Actions to Fix
|
18
tools/error-page-builder/src/main/resources/log4j2.xml
Normal file
18
tools/error-page-builder/src/main/resources/log4j2.xml
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="info">
|
||||
<Properties>
|
||||
<Property name="defaultLogLevel">${sys:defaultLogLevel:-info}</Property>
|
||||
<Property name="consoleLogLevel">${sys:consoleLogLevel:-error}</Property>
|
||||
</Properties>
|
||||
<Appenders>
|
||||
<Console name="Console-Appender" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="[%level{length=5}] %date{HH:mm:ssZ} - %msg%n%throwable{0}"/>
|
||||
</Console>
|
||||
</Appenders>
|
||||
|
||||
<Loggers>
|
||||
<Root level="${defaultLogLevel}">
|
||||
<AppenderRef ref="Console-Appender" level="${consoleLogLevel}"/>
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
@ -0,0 +1,45 @@
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import net.corda.errorPageBuilder.ErrorTableGenerator
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.nio.file.Paths
|
||||
import java.util.*
|
||||
|
||||
class ErrorTableGeneratorTest {
|
||||
|
||||
companion object {
|
||||
private val RESOURCE_LOCATION = Paths.get("src/test/resources/test-errors").toAbsolutePath().toFile()
|
||||
}
|
||||
|
||||
private val englishTable = """| Error Code | Aliases | Description | Actions to Fix |
|
||||
/| ---------- | ------- | ----------- | -------------- |
|
||||
/| test-error | foo, bar | Test description | Actions |
|
||||
""".trimMargin("/")
|
||||
|
||||
private val irishTable = """| Cód Earráide | Ailiasanna | Cur síos | Caingne le Deisiú |
|
||||
/| ------------ | ---------- | -------- | ----------------- |
|
||||
/| test-error | foo, bar | Teachtaireacht tástála | Roinnt gníomhartha |
|
||||
""".trimMargin("/")
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `check error table is produced as expected`() {
|
||||
val generator = ErrorTableGenerator(RESOURCE_LOCATION, Locale.forLanguageTag("en-US"))
|
||||
val table = generator.generateMarkdown()
|
||||
// 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)
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `check table in other locales is produced as expected`() {
|
||||
val generator = ErrorTableGenerator(RESOURCE_LOCATION, Locale.forLanguageTag("ga-IE"))
|
||||
val table = generator.generateMarkdown()
|
||||
assertEquals(irishTable.split("\n").joinToString(System.lineSeparator()), table)
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException::class, timeout = 300_000)
|
||||
fun `error thrown if unknown directory passed to generator`() {
|
||||
val generator = ErrorTableGenerator(File("not/a/directory"), Locale.getDefault())
|
||||
generator.generateMarkdown()
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
codeHeading = Error Code
|
||||
aliasesHeading = Aliases
|
||||
descriptionHeading = Description
|
||||
toFixHeading = Actions to Fix
|
@ -0,0 +1,4 @@
|
||||
codeHeading = Cód Earráide
|
||||
aliasesHeading = Ailiasanna
|
||||
descriptionHeading = Cur síos
|
||||
toFixHeading = Caingne le Deisiú
|
@ -0,0 +1,4 @@
|
||||
errorTemplate = This is a test message
|
||||
shortDescription = Test description
|
||||
actionsToFix = Actions
|
||||
aliases = foo, bar
|
@ -0,0 +1,3 @@
|
||||
errorTemplate = Is teachtaireacht earráide é seo
|
||||
shortDescription = Teachtaireacht tástála
|
||||
actionsToFix = Roinnt gníomhartha
|
Loading…
Reference in New Issue
Block a user