This is an automated email from the ASF dual-hosted git repository. jungm pushed a commit to branch ee11 in repository https://gitbox.apache.org/repos/asf/tomee-tck.git
commit 6fa854196031494657e340b0dee3e1c0db956801 Author: Markus Jung <[email protected]> AuthorDate: Sat Jul 18 23:06:21 2026 +0200 Run the WebSocket TCK negative-deployment tests against TomEE Tolerate the spec-required deployment failure of the negdep archives with an Arquillian extension so their client probes run, and set the two Tomcat WebSocket timeout properties on both the client and server JVMs. The suite now discovers 737 tests with only the 3 permessage-deflate configurator assertions excluded. --- KNOWN_ISSUES.md | 21 ++- runner-standalone/README.md | 2 +- runner-standalone/exclusions/websocket.txt | 69 +++------ runner-standalone/websocket/pom.xml | 6 + .../NegativeDeploymentToleranceObserver.java | 169 +++++++++++++++++++++ .../websocket/WebSocketTckExtension.java | 17 +++ ...org.jboss.arquillian.core.spi.LoadableExtension | 1 + .../websocket/src/tomee-conf/system.properties | 21 +++ 8 files changed, 249 insertions(+), 57 deletions(-) diff --git a/KNOWN_ISSUES.md b/KNOWN_ISSUES.md index 7f8e727..b4832d3 100644 --- a/KNOWN_ISSUES.md +++ b/KNOWN_ISSUES.md @@ -39,7 +39,7 @@ Detail lives next to each runner: | debugging | passes (4 SMAPs validated) | — | — | | security | 132 tests, 5 F + 2 E; signature test passes | 7 tests | TomEE Jakarta Security | | authentication | 105 tests, 50 F; signature test passes | 50 methods (spi) | Tomcat AuthConfigFactory SPI | -| websocket | 715 tests, 30 E | 25 classes + 5 methods | Tomcat halts webapp deploy on invalid endpoints; extension/timeout behavior | +| websocket | 737 tests, 3 E | 3 methods | Client container advertises permessage-deflate in the negotiated extension lists | | faces (modern modules) | 263 tests on record, 9 F + 30 E | 39 tests | TomEE faces-config parsing + Mojarra integration | | faces-old (JavaTest) | 5,391 tests, all pass (recorded run: 5 F from a foreign server answering :8080 mid-run; pass on re-run) | — | — (standalone mode, no exclusions) | | faces-signaturetest | passes against Plume's Mojarra (org.glassfish:jakarta.faces 4.1.9) | — | — | @@ -108,13 +108,18 @@ Fixes belong in Apache TomEE (or Tomcat); each removes exclusion entries. 11. **webprofile ZIP signature leak** — the combined `jakartaee-api` jar exposes Jakarta Batch and Messaging packages although `javaee.level=web` does not declare them; strip them or declare and certify them. -12. **WebSocket 2.2 behavior (Tomcat)** — a WAR containing an invalid - server endpoint fails the whole webapp deployment (the spec-required - deployment halt, but the TCK's Arquillian harness reports the failed - deploy as an error; 25 negative-deployment classes), the server-side - configurator observes Tomcat's built-in `permessage-deflate` in the - requested/negotiated extension lists (3 tests), and two idle-timeout/ - close-code assertions differ. +12. **WebSocket 2.2 extension advertising (Tomcat)** — the server-side + configurator reports the extensions the client requested and negotiated. + TomEE's client-side WebSocket container (Tomcat's `tomcat-websocket`) + always advertises its built-in `permessage-deflate` extension in the + opening handshake, so it appears in the requested and negotiated lists + the configurator observes while the test expects only the extensions it + declared (3 methods in one class). The failure reproduces when the class + runs on its own, so it is a Tomcat/TomEE client-container behavior to + triage upstream. The negative-deployment classes (an invalid server + endpoint aborts the whole webapp deployment, as the spec requires) run + and pass: the runner's Arquillian extension tolerates the deployment + failure so each client probe still runs. [websocket.txt](runner-standalone/exclusions/websocket.txt). 13. **Bean Validation XML config broken on stock Plume** — the Plume distribution ships EclipseLink MOXy (`eclipselink-5.0.1.jar`) and the JAXB diff --git a/runner-standalone/README.md b/runner-standalone/README.md index 019ca2a..df31c52 100644 --- a/runner-standalone/README.md +++ b/runner-standalone/README.md @@ -57,7 +57,7 @@ derived from; with the default exclusions applied these suites run green. | Servlet 6.1 | EFTL zip (installed as `jakarta.tck:servlet-tck-runtime:6.1.0`) | `servlet` | **Runs: 1,706 tests, 12 errors, 7 skipped** (2026-07-18). The harness is complete: slf4j-simple resolves at the TCK-derived version, the TCK's bundled client certificate is trusted and mapped (both client-cert tests pass, including the https-targeted deployment via a metadata observer reporting the TLS port), cross-context dispatch is enabled and the STRICT_SERVLET_COMPLIANCE subset (`alwaysAcce [...] | Pages 4.0 | EFTL zip (installed as `jakarta.tck:jakarta-pages-tck:4.0.0`) | `pages` | **Passes 682/682 (0 failures, 0 errors, signature test included, no exclusions)** (2026-07-18). Needs the Central `jakarta.tck:common`/`signaturetest` support line at 11.1.1 with sigtest 2.6 (the TCK pom's own pins depend on the unpublished JavaTest harness), and the overlay removes TomEE's global UTF-8 default encodings from conf/web.xml because the TCK asserts the spec default ISO-8859-1. Its TomEE/ [...] | Validation 3.1 | EFTL zip (installed as `jakarta.validation:validation-tck-tests:3.1.1`) | `validation` | **Runs: 1,049 tests, 0 failures, no exclusions; the mandatory API signature test runs and passes** against the `jakarta.validation` packages the Plume distribution ships. Needs AssertJ 3.7.0 pinned (the published TCK jar is compiled against its covariant signatures) and the JAXB RI pinned as `jakarta.xml.bind.JAXBContextFactory` in the server JVM (Plume ships both MOXy and the RI; [...] -| WebSocket 2.2 | EFTL zip (installed as `jakarta.tck:websocket-tck-spec-tests:2.2.0`) | `websocket` | **Runs: 715 tests, 0 failures, 30 errors** (2026-07-18) against Tomcat's WebSocket implementation, driven through the `tomcat-websocket` client container; the signature test passes. 25 errors are the server-side negative-deployment classes: Tomcat halts the whole webapp deployment on an invalid endpoint (the spec-required halt, but Arquillian reports the failed deploy as an error before [...] +| WebSocket 2.2 | EFTL zip (installed as `jakarta.tck:websocket-tck-spec-tests:2.2.0`) | `websocket` | **Runs: 737 tests, 0 failures, 3 errors** (2026-07-18) against Tomcat's WebSocket implementation, driven through the `tomcat-websocket` client container; the signature test passes. The negative-deployment classes run and pass: an invalid server endpoint aborts the whole webapp deployment as the spec requires, and the runner's Arquillian extension (`NegativeDeploymentToleranceObserver`) [...] | Security 4.0 | Source reactor zip 4.0.1 | `security` (Maven module) | **Runs: 26 app modules plus the signature test, 132 tests, 5 failures, 2 errors at baseline; green with the reviewed exclusions (all 27 invoker projects pass, verified end-to-end 2026-07-18)**. The runner downloads and patches the reactor, injects a tomee-remote profile (including the `trustStore.path`/`trustStore.password` properties the OpenID modules' keytool steps read), and drives every module through the Maven [...] | Authentication 3.1 | Source reactor zip 3.1.2 | `authentication` (Maven module) | **Runs: 12 Web Profile modules plus the signature test; green with the reviewed exclusions (all 13 invoker projects pass, 0 failures, verified end-to-end 2026-07-18)**. The spi module's 50 excluded assertions are Tomcat AuthConfigFactory SPI conformance gaps — product results to triage. The signature test passes against `org.apache.tomcat:tomcat-jaspic-api` (the distribution's `lib/jaspic-api.jar`), with [...] | Faces 4.1 | Source reactor zip 4.1.2 | `faces` (Maven module) | **Runs: modern Arquillian modules — 263 tests on record, 9 failures + 30 errors** (2026-07-18) on Plume's Mojarra. The dominant product finding: TomEE's faces-config.xml unmarshaller rejects the `xsi:schemaLocation` attribute used by Faces 4.1 descriptors, failing those deployments (the 31 class exclusions in `exclusions/faces.txt`). The `faces-signaturetest` module runs in the same invoker pass and passes against the Moja [...] diff --git a/runner-standalone/exclusions/websocket.txt b/runner-standalone/exclusions/websocket.txt index 4691f2c..9e6572c 100644 --- a/runner-standalone/exclusions/websocket.txt +++ b/runner-standalone/exclusions/websocket.txt @@ -1,57 +1,30 @@ # Jakarta WebSocket 2.2 TCK (jakarta.tck:websocket-tck-spec-tests:2.2.0) on -# the TomEE Plume snapshot, Java 21, 2026-07-18: 715 tests, 0 failures, -# 30 errors, 0 skipped. The signature test passes. +# the TomEE Plume snapshot, Java 21: 737 tests, 0 failures, 3 errors, +# 0 skipped. The signature test passes. # -# The errors cluster in two Tomcat behaviors: -# - 25 negative-deployment classes (negdep server-side): a WAR containing an -# invalid server endpoint fails the whole webapp deployment on Tomcat. -# The spec requires the deployment error to halt the application -# deployment, so this is arguably compliant-but-stricter behavior, but -# Arquillian surfaces the failed deployment as a test error before the -# probe can run. The client-side negdep classes all pass. -# - 5 behavioral differences: the server-side configurator observes -# extension lists that include Tomcat's built-in permessage-deflate -# (3 getNegotiatedExtensions* tests), and two timeout tests -# (session setTimeout2Test, spec closing status-1006-after-timeout) -# observe different idle-timeout/close-code behavior. -# These are Tomcat/TomEE product results to triage upstream. +# The 3 errors are one behavioral difference in a single class. The +# server-side configurator reports the extensions the client requested and +# the extensions it negotiated. TomEE's client-side WebSocket container +# (Tomcat's tomcat-websocket) always advertises its built-in +# permessage-deflate extension in the opening handshake, so that extension +# appears in the requested and negotiated lists the configurator observes, +# while the test expects only the extensions it declared. The failure +# reproduces when the class is run on its own, so it is a genuine +# Tomcat/TomEE client-container behavior to triage upstream, not a harness +# artifact. +# +# The negative-deployment classes (the negdep package) now run and pass: an +# invalid server endpoint aborts the whole webapp deployment as the spec +# requires, and the runner's Arquillian extension +# (NegativeDeploymentToleranceObserver) tolerates that deployment failure so +# each client probe still runs. See runner-standalone/websocket for the +# extension. # # Format: maven-surefire excludesFile patterns, applied through # -Dtck.exclusions.file (see runner-standalone/README.md). -# Negative-deployment tests: Tomcat halts the whole webapp deployment -**/websocket/negdep/invalidpathparamtype/srv/onclose/WSCClientIT.java -**/websocket/negdep/invalidpathparamtype/srv/onerror/WSCClientIT.java -**/websocket/negdep/invalidpathparamtype/srv/onmessage/WSCClientIT.java -**/websocket/negdep/invalidpathparamtype/srv/onopen/WSCClientIT.java -**/websocket/negdep/malformedpath/WSCClientIT.java -**/websocket/negdep/multiplepaths/WSCClientIT.java -**/websocket/negdep/onclose/srv/duplicate/WSCClientIT.java -**/websocket/negdep/onclose/srv/toomanyargs/WSCClientIT.java -**/websocket/negdep/onerror/srv/duplicate/WSCClientIT.java -**/websocket/negdep/onerror/srv/toomanyargs/WSCClientIT.java -**/websocket/negdep/onmessage/pasrv/nomoreendpoints/WSCClientIT.java -**/websocket/negdep/onmessage/ppsrv/nomoreendpoints/WSCClientIT.java -**/websocket/negdep/onmessage/srv/binarybytebufferint/WSCClientIT.java -**/websocket/negdep/onmessage/srv/binaryduplicate/WSCClientIT.java -**/websocket/negdep/onmessage/srv/binaryinputstreamboolean/WSCClientIT.java -**/websocket/negdep/onmessage/srv/binarynodecoder/WSCClientIT.java -**/websocket/negdep/onmessage/srv/pongboolean/WSCClientIT.java -**/websocket/negdep/onmessage/srv/pongduplicate/WSCClientIT.java -**/websocket/negdep/onmessage/srv/textbigdecimal/WSCClientIT.java -**/websocket/negdep/onmessage/srv/textduplicate/WSCClientIT.java -**/websocket/negdep/onmessage/srv/textnodecoder/WSCClientIT.java -**/websocket/negdep/onmessage/srv/textreaderboolean/WSCClientIT.java -**/websocket/negdep/onmessage/srv/textstringint/WSCClientIT.java -**/websocket/negdep/onopen/srv/duplicate/WSCClientIT.java -**/websocket/negdep/onopen/srv/toomanyargs/WSCClientIT.java - -# Extension negotiation: the configurator observes permessage-deflate in -# the requested/negotiated lists +# Extension negotiation: the configurator observes the client container's +# built-in permessage-deflate in the requested/negotiated lists **/websocket/ee/jakarta/websocket/server/serverendpointconfig/configurator/WSCClientIT.java#getNegotiatedExtensionsRequestedTest **/websocket/ee/jakarta/websocket/server/serverendpointconfig/configurator/WSCClientIT.java#getNegotiatedExtensionsResultedTest **/websocket/ee/jakarta/websocket/server/serverendpointconfig/configurator/WSCClientIT.java#getNegotiatedExtensionsOrderedByRequestTest - -# Idle-timeout and close-code behavioral differences -**/websocket/ee/jakarta/websocket/session/WSClientIT.java#setTimeout2Test -**/websocket/spec/application/closing/WSCClientIT.java#connectionHasBeenClosedWithStatus1006AfterTimeoutTest diff --git a/runner-standalone/websocket/pom.xml b/runner-standalone/websocket/pom.xml index 400700f..4c57d07 100644 --- a/runner-standalone/websocket/pom.xml +++ b/runner-standalone/websocket/pom.xml @@ -160,6 +160,12 @@ <webServerHost>localhost</webServerHost> <webServerPort>${tomee.http.port}</webServerPort> <ws_wait>${websocket.tck.ws.wait}</ws_wait> + <!-- tomcat-websocket is the TCK's client-side WebSocket container in + this JVM; the abnormal-close timeout tests need it to poll idle + sessions on a short period and to bound the 1006 close send. + Mirrors the server-side overlay in src/tomee-conf. --> + <org.apache.tomcat.websocket.DEFAULT_PROCESS_PERIOD>0</org.apache.tomcat.websocket.DEFAULT_PROCESS_PERIOD> + <org.apache.tomcat.websocket.ABNORMAL_SESSION_CLOSE_SEND_TIMEOUT>1000</org.apache.tomcat.websocket.ABNORMAL_SESSION_CLOSE_SEND_TIMEOUT> <!-- Strict mode for the negative-deployment tests: an invalid endpoint left in service is a failure. --> <lib.name>cts</lib.name> diff --git a/runner-standalone/websocket/src/test/java/org/apache/tomee/tck/standalone/websocket/NegativeDeploymentToleranceObserver.java b/runner-standalone/websocket/src/test/java/org/apache/tomee/tck/standalone/websocket/NegativeDeploymentToleranceObserver.java new file mode 100644 index 0000000..87b1ced --- /dev/null +++ b/runner-standalone/websocket/src/test/java/org/apache/tomee/tck/standalone/websocket/NegativeDeploymentToleranceObserver.java @@ -0,0 +1,169 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0. + */ +package org.apache.tomee.tck.standalone.websocket; + +import org.jboss.arquillian.container.spi.client.container.DeploymentException; +import org.jboss.arquillian.container.spi.client.deployment.Deployment; +import org.jboss.arquillian.container.spi.client.deployment.DeploymentDescription; +import org.jboss.arquillian.container.spi.client.protocol.metadata.HTTPContext; +import org.jboss.arquillian.container.spi.client.protocol.metadata.ProtocolMetaData; +import org.jboss.arquillian.container.spi.client.protocol.metadata.Servlet; +import org.jboss.arquillian.container.spi.context.annotation.DeploymentScoped; +import org.jboss.arquillian.container.spi.event.DeployDeployment; +import org.jboss.arquillian.container.spi.event.UnDeployDeployment; +import org.jboss.arquillian.container.spi.event.container.BeforeDeploy; +import org.jboss.arquillian.core.api.InstanceProducer; +import org.jboss.arquillian.core.api.annotation.Inject; +import org.jboss.arquillian.core.api.annotation.Observes; +import org.jboss.arquillian.core.spi.EventContext; + +/** + * The WebSocket TCK's negative-deployment tests (the {@code negdep} package) + * deploy a WAR that contains an intentionally invalid server endpoint. The + * specification requires such an endpoint to abort the whole application + * deployment, so TomEE/Tomcat correctly fails the deploy. The + * {@code @Deployment} methods carry no {@code @ShouldThrowException}: each test + * instead expects to deploy, then probe over the network that the WAR's other, + * valid echo endpoint is unreachable (because the deployment was aborted). The + * clients read the target host and port from the {@code webServerHost} and + * {@code webServerPort} system properties. + * + * <p>Left alone, Arquillian's {@code DeploymentExceptionHandler} finds a + * deployment error with no expected exception and surfaces it as a class-level + * error before the client test method can run. This observer makes the harness + * tolerate the spec-required failure for {@code negdep} archives, in three + * pieces: + * <ul> + * <li>a {@link BeforeDeploy} observer (fired inside the still-open + * deployment-scoped context, before the container attempts the deploy) + * publishes a {@link ProtocolMetaData} pointing at the running TomEE, so + * the {@code @ArquillianResource URL} the client base class declares + * resolves even though the deploy will fail;</li> + * <li>a {@link DeployDeployment} interceptor tolerates the + * {@link DeploymentException} the failed deploy raises and clears the + * error recorded on the deployment, so the client test method runs;</li> + * <li>an {@link UnDeployDeployment} interceptor tolerates the matching + * undeploy failure, since the archive never actually deployed.</li> + * </ul> + * Every other deployment is left untouched, so a genuine deployment failure in + * any non-negdep test still fails that test. + */ +public class NegativeDeploymentToleranceObserver { + + private static final String HOST = System.getProperty("webServerHost", "localhost"); + private static final int PORT = Integer.getInteger("webServerPort", 8080); + + @Inject + @DeploymentScoped + private InstanceProducer<ProtocolMetaData> protocolMetaData; + + /** + * Runs inside the deployment-scoped context before the container attempts + * the deploy. Pre-seed server metadata for negdep archives so the client's + * injected URL resolves after the deploy is (expectedly) aborted. + */ + public void seedNegativeDeploymentMetaData(@Observes final BeforeDeploy event) { + final DeploymentDescription description = event.getDeployment(); + if (!isNegativeDeployment(description)) { + return; + } + final HTTPContext http = new HTTPContext(HOST, PORT); + http.add(new Servlet("default", contextRoot(description))); + protocolMetaData.set(new ProtocolMetaData().addContext(http)); + } + + public void tolerateNegativeDeployment(@Observes final EventContext<DeployDeployment> context) { + final Deployment deployment = context.getEvent().getDeployment(); + if (!isNegativeDeployment(deployment.getDescription())) { + context.proceed(); + return; + } + try { + context.proceed(); + } catch (final Throwable thrown) { + // The failed deploy raises the container's checked + // DeploymentException (sneaky-thrown); tolerate it and clear the + // error recorded on the deployment so the client test runs its + // probe. Re-raise anything that is not a deployment failure. + if (!isDeploymentFailure(thrown)) { + rethrow(thrown); + } + } + clearDeploymentError(deployment); + } + + public void tolerateNegativeUndeployment(@Observes final EventContext<UnDeployDeployment> context) { + if (!isNegativeDeployment(context.getEvent().getDeployment().getDescription())) { + context.proceed(); + return; + } + // The negdep archive never actually deployed, so the undeploy has + // nothing to remove and fails; swallow that failure only. + try { + context.proceed(); + } catch (final Throwable thrown) { + if (!isDeploymentFailure(thrown)) { + rethrow(thrown); + } + } + } + + private static String contextRoot(final DeploymentDescription description) { + String name = description.getArchive().getName(); + if (name.endsWith(".war")) { + name = name.substring(0, name.length() - ".war".length()); + } + return name; + } + + private static boolean isNegativeDeployment(final DeploymentDescription description) { + if (description == null) { + return false; + } + final String name = description.getName(); + if (name != null && name.contains("negdep")) { + return true; + } + if (description.getArchive() != null) { + final String archiveName = description.getArchive().getName(); + return archiveName != null && archiveName.contains("negdep"); + } + return false; + } + + @SuppressWarnings("unchecked") + private static <T extends Throwable> void rethrow(final Throwable throwable) throws T { + throw (T) throwable; + } + + private static boolean isDeploymentFailure(final Throwable throwable) { + for (Throwable current = throwable; current != null; current = current.getCause()) { + if (current instanceof DeploymentException) { + return true; + } + } + return false; + } + + private static void clearDeploymentError(final Deployment deployment) { + if (deployment.hasDeploymentError()) { + // Deployment exposes no setter to clear the recorded error; + // marking it deployed leaves the error in place, so reset it + // reflectively. The field name is stable across the Arquillian + // 1.x container SPI. + try { + final java.lang.reflect.Field field = Deployment.class.getDeclaredField("deploymentError"); + field.setAccessible(true); + field.set(deployment, null); + } catch (final ReflectiveOperationException e) { + throw new IllegalStateException( + "Unable to clear the recorded deployment error for a WebSocket negdep archive", e); + } + } + deployment.deployed(); + } +} diff --git a/runner-standalone/websocket/src/test/java/org/apache/tomee/tck/standalone/websocket/WebSocketTckExtension.java b/runner-standalone/websocket/src/test/java/org/apache/tomee/tck/standalone/websocket/WebSocketTckExtension.java new file mode 100644 index 0000000..1bd84bf --- /dev/null +++ b/runner-standalone/websocket/src/test/java/org/apache/tomee/tck/standalone/websocket/WebSocketTckExtension.java @@ -0,0 +1,17 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0. + */ +package org.apache.tomee.tck.standalone.websocket; + +import org.jboss.arquillian.core.spi.LoadableExtension; + +public class WebSocketTckExtension implements LoadableExtension { + + @Override + public void register(final ExtensionBuilder builder) { + builder.observer(NegativeDeploymentToleranceObserver.class); + } +} diff --git a/runner-standalone/websocket/src/test/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension b/runner-standalone/websocket/src/test/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension new file mode 100644 index 0000000..9297113 --- /dev/null +++ b/runner-standalone/websocket/src/test/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension @@ -0,0 +1 @@ +org.apache.tomee.tck.standalone.websocket.WebSocketTckExtension diff --git a/runner-standalone/websocket/src/tomee-conf/system.properties b/runner-standalone/websocket/src/tomee-conf/system.properties new file mode 100644 index 0000000..6d1fdc3 --- /dev/null +++ b/runner-standalone/websocket/src/tomee-conf/system.properties @@ -0,0 +1,21 @@ +# Strict behavior is preferable for compatibility testing. +org.apache.catalina.STRICT_SERVLET_COMPLIANCE=true +user.language=en +user.country=US +user.timezone=UTC + +# Required by the remote TomEE Arquillian container. +tomee.remote.support=true +openejb.system.apps=true + +# Do not scan the runner or database implementation as deployments. +openejb.deployments.classpath=false +openejb.classloader.forced-skip=org.apache.derby. + +# The abnormal-session-close timeout tests (session setTimeout2Test, spec +# closing status-1006-after-timeout) require the server-side Tomcat WebSocket +# container to poll for idle sessions on a short period and to bound the +# time spent sending the abnormal 1006 close. Match the values the TCK's +# certified Tomcat run uses so the client observes the close within ws_wait. +org.apache.tomcat.websocket.DEFAULT_PROCESS_PERIOD=0 +org.apache.tomcat.websocket.ABNORMAL_SESSION_CLOSE_SEND_TIMEOUT=1000
