001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.jmeter.assertions;
020:
021: import java.io.Serializable;
022: import java.util.ArrayList;
023:
024: import org.apache.jmeter.samplers.SampleResult;
025: import org.apache.jmeter.testelement.AbstractTestElement;
026: import org.apache.jmeter.testelement.property.CollectionProperty;
027: import org.apache.jmeter.testelement.property.IntegerProperty;
028: import org.apache.jmeter.testelement.property.JMeterProperty;
029: import org.apache.jmeter.testelement.property.NullProperty;
030: import org.apache.jmeter.testelement.property.PropertyIterator;
031: import org.apache.jmeter.testelement.property.StringProperty;
032: import org.apache.jmeter.util.JMeterUtils;
033: import org.apache.jorphan.logging.LoggingManager;
034: import org.apache.log.Logger;
035: import org.apache.oro.text.MalformedCachePatternException;
036: import org.apache.oro.text.regex.Pattern;
037: import org.apache.oro.text.regex.Perl5Compiler;
038: import org.apache.oro.text.regex.Perl5Matcher;
039:
040: // @see org.apache.jmeter.assertions.PackageTest for unit tests
041:
042: /**
043: * Test element to handle Response Assertions, @see AssertionGui
044: */
045: public class ResponseAssertion extends AbstractTestElement implements
046: Serializable, Assertion {
047: private static final Logger log = LoggingManager
048: .getLoggerForClass();
049:
050: private final static String TEST_FIELD = "Assertion.test_field"; // $NON-NLS-1$
051:
052: // Values for TEST_FIELD
053: // N.B. we cannot change the text value as it is in test plans
054: private final static String SAMPLE_URL = "Assertion.sample_label"; // $NON-NLS-1$
055:
056: private final static String RESPONSE_DATA = "Assertion.response_data"; // $NON-NLS-1$
057:
058: private final static String RESPONSE_CODE = "Assertion.response_code"; // $NON-NLS-1$
059:
060: private final static String RESPONSE_MESSAGE = "Assertion.response_message"; // $NON-NLS-1$
061:
062: private final static String RESPONSE_HEADERS = "Assertion.response_headers"; // $NON-NLS-1$
063:
064: private final static String ASSUME_SUCCESS = "Assertion.assume_success"; // $NON-NLS-1$
065:
066: private final static String TEST_STRINGS = "Asserion.test_strings"; // $NON-NLS-1$
067:
068: private final static String TEST_TYPE = "Assertion.test_type"; // $NON-NLS-1$
069:
070: /*
071: * Mask values for TEST_TYPE TODO: remove either MATCH or CONTAINS - they
072: * are mutually exckusive
073: */
074: private final static int MATCH = 1 << 0;
075:
076: final static int CONTAINS = 1 << 1;
077:
078: private final static int NOT = 1 << 2;
079:
080: private final static int EQUALS = 1 << 3;
081:
082: private static final int EQUALS_SECTION_DIFF_LEN = JMeterUtils
083: .getPropDefault("assertion.equals_section_diff_len", 100);
084:
085: /** Signifies truncated text in diff display. */
086: private static final String EQUALS_DIFF_TRUNC = "...";
087:
088: private static final String RECEIVED_STR = "****** received : ";
089: private static final String COMPARISON_STR = "****** comparison: ";
090: private static final String DIFF_DELTA_START = JMeterUtils
091: .getPropDefault("assertion.equals_diff_delta_start", "[[[");
092: private static final String DIFF_DELTA_END = JMeterUtils
093: .getPropDefault("assertion.equals_diff_delta_end", "]]]");
094:
095: public ResponseAssertion() {
096: setProperty(new CollectionProperty(TEST_STRINGS,
097: new ArrayList()));
098: }
099:
100: public void clear() {
101: super .clear();
102: setProperty(new CollectionProperty(TEST_STRINGS,
103: new ArrayList()));
104: }
105:
106: private void setTestField(String testField) {
107: setProperty(TEST_FIELD, testField);
108: }
109:
110: public void setTestFieldURL() {
111: setTestField(SAMPLE_URL);
112: }
113:
114: public void setTestFieldResponseCode() {
115: setTestField(RESPONSE_CODE);
116: }
117:
118: public void setTestFieldResponseData() {
119: setTestField(RESPONSE_DATA);
120: }
121:
122: public void setTestFieldResponseMessage() {
123: setTestField(RESPONSE_MESSAGE);
124: }
125:
126: public void setTestFieldResponseHeaders() {
127: setTestField(RESPONSE_HEADERS);
128: }
129:
130: public boolean isTestFieldURL() {
131: return SAMPLE_URL.equals(getTestField());
132: }
133:
134: public boolean isTestFieldResponseCode() {
135: return RESPONSE_CODE.equals(getTestField());
136: }
137:
138: public boolean isTestFieldResponseData() {
139: return RESPONSE_DATA.equals(getTestField());
140: }
141:
142: public boolean isTestFieldResponseMessage() {
143: return RESPONSE_MESSAGE.equals(getTestField());
144: }
145:
146: public boolean isTestFieldResponseHeaders() {
147: return RESPONSE_HEADERS.equals(getTestField());
148: }
149:
150: private void setTestType(int testType) {
151: setProperty(new IntegerProperty(TEST_TYPE, testType));
152: }
153:
154: public void addTestString(String testString) {
155: getTestStrings().addProperty(
156: new StringProperty(String
157: .valueOf(testString.hashCode()), testString));
158: }
159:
160: public void clearTestStrings() {
161: getTestStrings().clear();
162: }
163:
164: public AssertionResult getResult(SampleResult response) {
165: AssertionResult result;
166:
167: // None of the other Assertions check the response status, so remove
168: // this check
169: // for the time being, at least...
170: // if (!response.isSuccessful())
171: // {
172: // result = new AssertionResult();
173: // result.setError(true);
174: // byte [] ba = response.getResponseData();
175: // result.setFailureMessage(
176: // ba == null ? "Unknown Error (responseData is empty)" : new String(ba)
177: // );
178: // return result;
179: // }
180:
181: result = evaluateResponse(response);
182: return result;
183: }
184:
185: /***************************************************************************
186: * !ToDoo (Method description)
187: *
188: * @return !ToDo (Return description)
189: **************************************************************************/
190: public String getTestField() {
191: return getPropertyAsString(TEST_FIELD);
192: }
193:
194: /***************************************************************************
195: * !ToDoo (Method description)
196: *
197: * @return !ToDo (Return description)
198: **************************************************************************/
199: public int getTestType() {
200: JMeterProperty type = getProperty(TEST_TYPE);
201: if (type instanceof NullProperty) {
202: return CONTAINS;
203: }
204: return type.getIntValue();
205: }
206:
207: /***************************************************************************
208: * !ToDoo (Method description)
209: *
210: * @return !ToDo (Return description)
211: **************************************************************************/
212: public CollectionProperty getTestStrings() {
213: return (CollectionProperty) getProperty(TEST_STRINGS);
214: }
215:
216: public boolean isEqualsType() {
217: return (getTestType() & EQUALS) > 0;
218: }
219:
220: public boolean isContainsType() {
221: return (getTestType() & CONTAINS) > 0;
222: }
223:
224: public boolean isMatchType() {
225: return (getTestType() & MATCH) > 0;
226: }
227:
228: public boolean isNotType() {
229: return (getTestType() & NOT) > 0;
230: }
231:
232: public void setToContainsType() {
233: setTestType((getTestType() | CONTAINS) & ~(MATCH | EQUALS));
234: }
235:
236: public void setToMatchType() {
237: setTestType((getTestType() | MATCH) & ~(CONTAINS | EQUALS));
238: }
239:
240: public void setToEqualsType() {
241: setTestType((getTestType() | EQUALS) & ~(MATCH | CONTAINS));
242: }
243:
244: public void setToNotType() {
245: setTestType((getTestType() | NOT));
246: }
247:
248: public void unsetNotType() {
249: setTestType(getTestType() & ~NOT);
250: }
251:
252: public boolean getAssumeSuccess() {
253: return getPropertyAsBoolean(ASSUME_SUCCESS, false);
254: }
255:
256: public void setAssumeSuccess(boolean b) {
257: setProperty(ASSUME_SUCCESS, b);
258: }
259:
260: /**
261: * Make sure the response satisfies the specified assertion requirements.
262: *
263: * @param response
264: * an instance of SampleResult
265: * @return an instance of AssertionResult
266: */
267: AssertionResult evaluateResponse(SampleResult response) {
268: boolean pass = true;
269: boolean not = (NOT & getTestType()) > 0;
270: AssertionResult result = new AssertionResult(getName());
271: String toCheck = ""; // The string to check (Url or data)
272:
273: if (getAssumeSuccess()) {
274: response.setSuccessful(true);// Allow testing of failure codes
275: }
276:
277: // What are we testing against?
278: if (isTestFieldResponseData()) {
279: toCheck = response.getResponseDataAsString(); // (bug25052)
280: } else if (isTestFieldResponseCode()) {
281: toCheck = response.getResponseCode();
282: } else if (isTestFieldResponseMessage()) {
283: toCheck = response.getResponseMessage();
284: } else if (isTestFieldResponseHeaders()) {
285: toCheck = response.getResponseHeaders();
286: } else { // Assume it is the URL
287: toCheck = response.getSamplerData(); // TODO - is this where the URL is stored?
288: if (toCheck == null)
289: toCheck = "";
290: }
291:
292: if (toCheck.length() == 0) {
293: return result.setResultForNull();
294: }
295:
296: result.setFailure(false);
297: result.setError(false);
298:
299: boolean contains = isContainsType(); // do it once outside loop
300: boolean equals = isEqualsType();
301: boolean debugEnabled = log.isDebugEnabled();
302: if (debugEnabled) {
303: log.debug("Type:" + (contains ? "Contains" : "Match")
304: + (not ? "(not)" : ""));
305: }
306:
307: try {
308: // Get the Matcher for this thread
309: Perl5Matcher localMatcher = JMeterUtils.getMatcher();
310: PropertyIterator iter = getTestStrings().iterator();
311: while (iter.hasNext()) {
312: String stringPattern = iter.next().getStringValue();
313: Pattern pattern = JMeterUtils.getPatternCache()
314: .getPattern(stringPattern,
315: Perl5Compiler.READ_ONLY_MASK);
316: boolean found;
317: if (contains) {
318: found = localMatcher.contains(toCheck, pattern);
319: } else if (equals) {
320: found = toCheck.equals(stringPattern);
321: } else {
322: found = localMatcher.matches(toCheck, pattern);
323: }
324: pass = not ? !found : found;
325: if (!pass) {
326: if (debugEnabled) {
327: log.debug("Failed: " + pattern);
328: }
329: result.setFailure(true);
330: result.setFailureMessage(getFailText(stringPattern,
331: toCheck));
332: break;
333: }
334: if (debugEnabled) {
335: log.debug("Passed: " + pattern);
336: }
337: }
338: } catch (MalformedCachePatternException e) {
339: result.setError(true);
340: result.setFailure(false);
341: result.setFailureMessage("Bad test configuration " + e);
342: }
343: return result;
344: }
345:
346: /**
347: * Generate the failure reason from the TestType
348: *
349: * @param stringPattern
350: * @return the message for the assertion report
351: */
352: // TODO strings should be resources (but currently must not contain commas or the CSV file will be broken)
353: private String getFailText(String stringPattern, String toCheck) {
354:
355: StringBuffer sb = new StringBuffer(200);
356: sb.append("Test failed: ");
357:
358: if (isTestFieldResponseData()) {
359: sb.append("text");
360: } else if (isTestFieldResponseCode()) {
361: sb.append("code");
362: } else if (isTestFieldResponseMessage()) {
363: sb.append("message");
364: } else if (isTestFieldResponseHeaders()) {
365: sb.append("headers");
366: } else // Assume it is the URL
367: {
368: sb.append("URL");
369: }
370:
371: switch (getTestType()) {
372: case CONTAINS:
373: sb.append(" expected to contain ");
374: break;
375: case NOT | CONTAINS:
376: sb.append(" expected not to contain ");
377: break;
378: case MATCH:
379: sb.append(" expected to match ");
380: break;
381: case NOT | MATCH:
382: sb.append(" expected not to match ");
383: break;
384: case EQUALS:
385: sb.append(" expected to equal ");
386: break;
387: case NOT | EQUALS:
388: sb.append(" expected not to equal ");
389: break;
390: default:// should never happen...
391: sb.append(" expected something using ");
392: }
393:
394: sb.append("/");
395:
396: if (isEqualsType()) {
397: sb.append(equalsComparisonText(toCheck, stringPattern));
398: } else {
399: sb.append(stringPattern);
400: }
401:
402: sb.append("/");
403:
404: return sb.toString();
405: }
406:
407: private static String trunc(final boolean right, final String str) {
408: if (str.length() <= EQUALS_SECTION_DIFF_LEN)
409: return str;
410: else if (right)
411: return str.substring(0, EQUALS_SECTION_DIFF_LEN)
412: + EQUALS_DIFF_TRUNC;
413: else
414: return EQUALS_DIFF_TRUNC
415: + str.substring(str.length()
416: - EQUALS_SECTION_DIFF_LEN, str.length());
417: }
418:
419: /**
420: * Returns some helpful logging text to determine where equality between two strings
421: * is broken, with one pointer working from the front of the strings and another working
422: * backwards from the end.
423: *
424: * @param received String received from sampler.
425: * @param comparison String specified for "equals" response assertion.
426: * @return Two lines of text separated by newlines, and then forward and backward pointers
427: * denoting first position of difference.
428: */
429: private static StringBuffer equalsComparisonText(
430: final String received, final String comparison) {
431: final StringBuffer text;
432: int firstDiff;
433: int lastRecDiff = -1;
434: int lastCompDiff = -1;
435: final int recLength = received.length();
436: final int compLength = comparison.length();
437: final int minLength = Math.min(recLength, compLength);
438: final String startingEqSeq;
439: String recDeltaSeq = "";
440: String compDeltaSeq = "";
441: String endingEqSeq = "";
442: final StringBuffer pad;
443:
444: text = new StringBuffer(Math.max(recLength, compLength) * 2);
445: for (firstDiff = 0; firstDiff < minLength; firstDiff++)
446: if (received.charAt(firstDiff) != comparison
447: .charAt(firstDiff))
448: break;
449: if (firstDiff == 0)
450: startingEqSeq = "";
451: else
452: startingEqSeq = trunc(false, received.substring(0,
453: firstDiff));
454:
455: lastRecDiff = recLength - 1;
456: lastCompDiff = compLength - 1;
457:
458: while ((lastRecDiff > firstDiff)
459: && (lastCompDiff > firstDiff)
460: && received.charAt(lastRecDiff) == comparison
461: .charAt(lastCompDiff)) {
462: lastRecDiff--;
463: lastCompDiff--;
464: }
465: endingEqSeq = trunc(true, received.substring(lastRecDiff + 1,
466: recLength));
467: if (endingEqSeq.length() == 0) {
468: recDeltaSeq = trunc(true, received.substring(firstDiff,
469: recLength));
470: compDeltaSeq = trunc(true, comparison.substring(firstDiff,
471: compLength));
472: } else {
473: recDeltaSeq = trunc(true, received.substring(firstDiff,
474: lastRecDiff + 1));
475: compDeltaSeq = trunc(true, comparison.substring(firstDiff,
476: lastCompDiff + 1));
477: }
478: pad = new StringBuffer(Math.abs(recDeltaSeq.length()
479: - compDeltaSeq.length()));
480: for (int i = 0; i < pad.capacity(); i++)
481: pad.append(' ');
482: if (recDeltaSeq.length() > compDeltaSeq.length())
483: compDeltaSeq += pad.toString();
484: else
485: recDeltaSeq += pad.toString();
486:
487: text.append("\n\n");
488: text.append(RECEIVED_STR);
489: text.append(startingEqSeq);
490: text.append(DIFF_DELTA_START);
491: text.append(recDeltaSeq);
492: text.append(DIFF_DELTA_END);
493: text.append(endingEqSeq);
494: text.append("\n\n");
495: text.append(COMPARISON_STR);
496: text.append(startingEqSeq);
497: text.append(DIFF_DELTA_START);
498: text.append(compDeltaSeq);
499: text.append(DIFF_DELTA_END);
500: text.append(endingEqSeq);
501: text.append("\n\n");
502: return text;
503: }
504:
505: }
|