/* * 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(); } } }); } }