blob: defb3c11c1297c13cc4c32dd4daaf2c82148191c [file] [log] [blame]
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.user.client.rpc;
import com.google.gwt.core.client.GWT;
import com.google.gwt.junit.DoNotRunWith;
import com.google.gwt.junit.Platform;
import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.user.client.rpc.UnicodeEscapingService.InvalidCharacterException;
import java.util.ArrayList;
import java.util.List;
/**
* Test that any valid string can be sent via RPC in both directions.
*
* TODO(jat): make unpaired surrogates work properly if it is possible to do so
* on all browsers, then add them to this test.
*/
public class UnicodeEscapingTest extends GWTTestCase {
/** the size of a block of characters to test. */
private static final int CHARACTER_BLOCK_SIZE = 64;
/**
* When doing the non-BMP test, we don't test every block of characters
* because it takes too long - this is the increment to use. It is not a power
* of two so we alter the alignment of the block of characters we skip.
*/
private static final int NON_BMP_TEST_INCREMENT = 8192 + 64;
/** the time to wait for the test of a block of characters. */
private static final int TEST_FINISH_DELAY_MS = 500000;
/**
* Generates a string containing a sequence of code points.
*
* @param start first code point to include in the string
* @param end one past the last code point to include in the string
* @return a string containing all the requested code points
*/
public static String getStringContainingCharacterRange(int start, int end) {
StringBuffer buf = new StringBuffer();
for (int codePoint = start; codePoint < end; ++codePoint) {
if (Character.isSupplementaryCodePoint(codePoint)) {
buf.append(Character.toChars(codePoint));
} else {
buf.append((char) codePoint);
}
}
return buf.toString();
}
/**
* Verifies that the supplied string includes the requested code points.
*
* @param start first code point to include in the string
* @param end one past the last code point to include in the string
* @param str the string to test
* @throws InvalidCharacterException if a character doesn't match
* @throws RuntimeException if the string is too long
*/
public static void verifyStringContainingCharacterRange(int start, int end,
String str) throws InvalidCharacterException {
if (str == null) {
throw new NullPointerException("String is null");
}
int expectedLen = end - start;
int strLen = str.codePointCount(0, str.length());
for (int i = 0, codePoint = start; i < strLen; i = Character.offsetByCodePoints(
str, i, 1)) {
int strCodePoint = str.codePointAt(i);
if (strCodePoint != codePoint) {
throw new InvalidCharacterException(i, codePoint, strCodePoint);
}
++codePoint;
}
if (strLen < expectedLen) {
throw new InvalidCharacterException(strLen, start + strLen, -1);
} else if (expectedLen != strLen) {
throw new RuntimeException(
"Too many characters returned on block from U+"
+ Integer.toHexString(start) + " to U+"
+ Integer.toHexString(end) + ": expected=" + expectedLen
+ ", actual=" + strLen);
}
}
private static UnicodeEscapingServiceAsync getService() {
UnicodeEscapingServiceAsync service = GWT.create(UnicodeEscapingService.class);
ServiceDefTarget target = (ServiceDefTarget) service;
target.setServiceEntryPoint(GWT.getModuleBaseURL() + "unicodeEscape");
return service;
}
/** start of current block being tested. */
protected int current;
@Override
public String getModuleName() {
return "com.google.gwt.user.RPCSuite";
}
/**
* Generate strings containing ranges of characters and sends them to the
* server for verification. This ensures that client->server string escaping
* properly handles all BMP characters.
*
* Unpaired or improperly paired surrogates are not tested here, as some
* browsers refuse to accept them. Properly paired surrogates are tested in
* the non-BMP test.
*
* Note that this does not test all possible combinations, which might be an
* issue, particularly with combining marks, though they should be logically
* equivalent in that case.
*
* @throws InvalidCharacterException
*/
public void testClientToServerBMPHigh() throws InvalidCharacterException {
delayTestFinish(TEST_FINISH_DELAY_MS);
clientToServerVerifyRange(Character.MAX_SURROGATE + 1,
Character.MIN_SUPPLEMENTARY_CODE_POINT, CHARACTER_BLOCK_SIZE,
CHARACTER_BLOCK_SIZE);
}
/**
* Generate strings containing ranges of characters and sends them to the
* server for verification. This ensures that client->server string escaping
* properly handles all BMP characters.
*
* Unpaired or improperly paired surrogates are not tested here, as some
* browsers refuse to accept them. Properly paired surrogates are tested in
* the non-BMP test.
*
* Note that this does not test all possible combinations, which might be an
* issue, particularly with combining marks, though they should be logically
* equivalent in that case.
*
* @throws InvalidCharacterException
* HtmlUnit test failed intermittently in draft mode.
*/
@DoNotRunWith(Platform.HtmlUnitUnknown)
public void testClientToServerBMPLow() throws InvalidCharacterException {
delayTestFinish(TEST_FINISH_DELAY_MS);
clientToServerVerifyRange(Character.MIN_CODE_POINT,
Character.MIN_SURROGATE, CHARACTER_BLOCK_SIZE, CHARACTER_BLOCK_SIZE);
}
/**
* Generate strings containing ranges of characters and sends them to the
* server for verification. This ensures that client->server string escaping
* properly handles all non-BMP characters.
*
* Note that this does not test all possible combinations, which might be an
* issue, particularly with combining marks, though they should be logically
* equivalent in that case.
*
* @throws InvalidCharacterException
*/
public void testClientToServerNonBMP() throws InvalidCharacterException {
delayTestFinish(TEST_FINISH_DELAY_MS);
clientToServerVerifyRange(Character.MIN_SUPPLEMENTARY_CODE_POINT,
Character.MAX_CODE_POINT + 1, CHARACTER_BLOCK_SIZE,
NON_BMP_TEST_INCREMENT);
}
/**
* Requests strings of CHARACTER_RANGE_SIZE from the server and validates that
* the returned string length matches CHARACTER_RANGE_SIZE and that all of the
* characters remain intact.
*
* Note that this does not test all possible combinations, which might be an
* issue, particularly with combining marks, though they should be logically
* equivalent in that case. Surrogate characters are also not tested here,
* see {@link #disabled_testServerToClientBMPSurrogates()}.
*
* HtmlUnit test failed intermittently in draft mode.
*/
@DoNotRunWith(Platform.HtmlUnitUnknown)
public void testServerToClientBMP() {
delayTestFinish(TEST_FINISH_DELAY_MS);
serverToClientVerify(Character.MIN_CODE_POINT,
Character.MIN_SURROGATE, CHARACTER_BLOCK_SIZE,
CHARACTER_BLOCK_SIZE);
}
/**
* Requests strings of CHARACTER_RANGE_SIZE from the server and validates that
* the returned string length matches CHARACTER_RANGE_SIZE and that all of the
* characters remain intact. This test checks the range of surrogate
* characters, which are used to encode non-BMP characters as pairs of UTF16
* characters.
*
* Note that this does not test all possible combinations.
*/
@DoNotRunWith(Platform.HtmlUnitBug)
// TODO(jat): decide if we really want to specify this behavior since some
// browsers and OOPHM plugins have issues with it -- disabled for now
public void disabled_testServerToClientBMPSurrogates() {
delayTestFinish(TEST_FINISH_DELAY_MS);
serverToClientVerify(Character.MIN_SURROGATE,
Character.MIN_SUPPLEMENTARY_CODE_POINT, CHARACTER_BLOCK_SIZE,
CHARACTER_BLOCK_SIZE);
}
/**
* Requests strings of CHARACTER_RANGE_SIZE from the server and validates that
* the returned string length matches CHARACTER_RANGE_SIZE and that all of the
* characters remain intact. Note that this test verifies non-BMP characters
* (ie, those which are represented as pairs of surrogates).
*
* Note that this does not test all possible combinations, which might be an
* issue, particularly with combining marks, though they should be logically
* equivalent in that case.
*/
public void testServerToClientNonBMP() {
delayTestFinish(TEST_FINISH_DELAY_MS);
serverToClientVerify(Character.MIN_SUPPLEMENTARY_CODE_POINT,
Character.MAX_CODE_POINT + 1, CHARACTER_BLOCK_SIZE,
NON_BMP_TEST_INCREMENT);
}
protected void clientToServerVerifyRange(final int start, final int end,
final int size, final int step) throws InvalidCharacterException {
current = start;
int blockEnd = Math.min(end, current + size);
getService().verifyStringContainingCharacterRange(current, blockEnd,
getStringContainingCharacterRange(start, blockEnd),
new AsyncCallback<Boolean>() {
List<Throwable> fails = new ArrayList<Throwable>();
public void onFailure(Throwable caught) {
fails.add(caught);
onSuccess(false);
}
public void onSuccess(Boolean ignored) {
current += step;
if (current < end) {
delayTestFinish(TEST_FINISH_DELAY_MS);
int blockEnd = Math.min(end, current + size);
try {
getService().verifyStringContainingCharacterRange(current,
blockEnd,
getStringContainingCharacterRange(current, blockEnd), this);
} catch (InvalidCharacterException e) {
fails.add(e);
}
} else if (!fails.isEmpty()) {
StringBuilder msg = new StringBuilder();
for (Throwable t : fails) {
msg.append(t.getMessage()).append("\n");
}
TestSetValidator.rethrowException(new RuntimeException(
msg.toString()));
} else {
finishTest();
}
}
});
}
protected void serverToClientVerify(final int start, final int end,
final int size, final int step) {
current = start;
getService().getStringContainingCharacterRange(start,
Math.min(end, current + size), new AsyncCallback<String>() {
List<Throwable> fails = new ArrayList<Throwable>();
public void onFailure(Throwable caught) {
fails.add(caught);
nextBatch();
}
public void onSuccess(String str) {
try {
verifyStringContainingCharacterRange(current, Math.min(end,
current + size), str);
} catch (InvalidCharacterException e) {
fails.add(e);
}
nextBatch();
}
private void nextBatch() {
current += step;
if (current < end) {
delayTestFinish(TEST_FINISH_DELAY_MS);
getService().getStringContainingCharacterRange(current,
Math.min(end, current + size), this);
} else if (!fails.isEmpty()) {
StringBuilder msg = new StringBuilder();
for (Throwable t : fails) {
msg.append(t.getMessage()).append("\n");
}
TestSetValidator.rethrowException(new RuntimeException(
msg.toString()));
} else {
finishTest();
}
}
});
}
/**
* Verify that string encoding/decoding is lossless.
*/
private void echoVerify(final String str) {
delayTestFinish(TEST_FINISH_DELAY_MS);
getService().echo(str, new AsyncCallback<String>() {
@Override
public void onFailure(Throwable caught) {
TestSetValidator.rethrowException(caught);
}
@Override
public void onSuccess(String result) {
assertEquals(str, result);
finishTest();
}
});
}
/**
* Test that a NUL character followed by an octal character is encoded
* correctly. Encoding the NUL character simply as "\0" in this case
* would cause the recipient to see "\07" as a single octal escape sequence,
* rather than two separate characters.
*/
public void testEscapeNull() {
echoVerify("\u0000" + "7"); // split to emphasize two characters
}
/**
* Test that HTML special characters are encoded correctly.
*/
public void testEscapeHtml() {
echoVerify("<img src=x onerror=alert(1)>");
}
}