Coverage Report - org.jbehave.core.embedder.StoryTimeouts
 
Classes in this File Line Coverage Branch Coverage Complexity
StoryTimeouts
100%
34/34
100%
12/12
2.136
StoryTimeouts$DigitTimeoutParser
100%
4/4
N/A
2.136
StoryTimeouts$SimpleTimeoutParser
86%
20/23
62%
5/8
2.136
StoryTimeouts$StoryTimeout
93%
27/29
100%
8/8
2.136
StoryTimeouts$TimeoutFormatException
100%
2/2
N/A
2.136
StoryTimeouts$TimeoutParser
N/A
N/A
2.136
 
 1  
 package org.jbehave.core.embedder;
 2  
 
 3  
 import static java.util.Arrays.asList;
 4  
 import static java.util.regex.Pattern.compile;
 5  
 
 6  
 import java.util.ArrayList;
 7  
 import java.util.HashMap;
 8  
 import java.util.List;
 9  
 import java.util.Map;
 10  
 import java.util.regex.Matcher;
 11  
 import java.util.regex.Pattern;
 12  
 import java.util.regex.PatternSyntaxException;
 13  
 
 14  
 import org.apache.commons.lang.builder.ToStringBuilder;
 15  
 import org.apache.commons.lang.builder.ToStringStyle;
 16  
 import org.codehaus.plexus.util.StringUtils;
 17  
 import org.jbehave.core.model.Story;
 18  
 
 19  
 public class StoryTimeouts {
 20  
 
 21  
         private static final String COMMA = ",";
 22  
         private EmbedderControls embedderControls;
 23  
         private EmbedderMonitor embedderMonitor;
 24  40
         private List<TimeoutParser> parsers = new ArrayList<TimeoutParser>();
 25  
 
 26  
         public StoryTimeouts(EmbedderControls embedderControls,
 27  40
                         EmbedderMonitor embedderMonitor) {
 28  40
                 this.embedderControls = embedderControls;
 29  40
                 this.embedderMonitor = embedderMonitor;
 30  40
                 configureDefaultParsers();
 31  40
         }
 32  
 
 33  
         private void configureDefaultParsers() {
 34  40
                 this.parsers.addAll(asList(new SimpleTimeoutParser(), new DigitTimeoutParser()));
 35  40
         }
 36  
 
 37  
         public StoryTimeouts withParsers(TimeoutParser...parsers){
 38  26
                 this.parsers.addAll(0, asList(parsers));
 39  26
                 return this;
 40  
         }
 41  
         
 42  
         public long getTimeoutInSecs(Story story) {
 43  86
                 Map<String, StoryTimeout> timeouts = asMap(embedderControls
 44  43
                                 .storyTimeouts());
 45  
                 
 46  
                 // look for timeout by path
 47  43
                 for (StoryTimeout timeout : timeouts.values()) {
 48  56
                         if (timeout.allowedByPath(story.getPath())) {
 49  22
                                 long timeoutInSecs = timeout.getTimeoutInSecs();
 50  20
                                 embedderMonitor.usingTimeout(story.getName(), timeoutInSecs);
 51  20
                                 return timeoutInSecs;
 52  
                         }
 53  34
                 }
 54  
 
 55  
                 // look for default timeout
 56  21
                 for (StoryTimeout timeout : timeouts.values()) {
 57  22
                         if (timeout.isDefault()) {
 58  17
                                 long timeoutInSecs = timeout.getTimeoutInSecs();
 59  17
                                 embedderMonitor.usingTimeout(story.getName(), timeoutInSecs);
 60  17
                                 return timeoutInSecs;
 61  
                         }
 62  5
                 }
 63  
 
 64  
                 // default to 300 for backward compatibility
 65  4
                 long timeoutInSecs = 300;
 66  4
                 embedderMonitor.usingTimeout(story.getName(), timeoutInSecs);
 67  4
                 return timeoutInSecs;
 68  
         }
 69  
 
 70  
         private Map<String, StoryTimeout> asMap(String timeoutsAsString) {
 71  43
                 Map<String, StoryTimeout> timeouts = new HashMap<String, StoryTimeout>();
 72  43
                 if (StringUtils.isBlank(timeoutsAsString)) {
 73  1
                         return timeouts;
 74  
                 }                
 75  106
                 for (String timeoutAsString : timeoutsAsString.split(COMMA)) {
 76  64
                         StoryTimeout timeout = new StoryTimeout(timeoutAsString, parsers);
 77  64
                         timeouts.put(timeout.getPathPattern(), timeout);
 78  
                 }
 79  42
                 return timeouts;
 80  
         }
 81  
 
 82  
         public static class StoryTimeout {
 83  
                 private static final String COLON = ":";
 84  
                 private boolean isDefault;
 85  64
                 private String pathPattern = "";
 86  64
                 private String timeout = "0";
 87  
                 private String timeoutAsString;
 88  
                 private List<TimeoutParser> parsers;
 89  
         
 90  64
                 public StoryTimeout(String timeoutAsString, List<TimeoutParser> parsers) {
 91  64
                         this.timeoutAsString = timeoutAsString;
 92  64
                         this.parsers = parsers;
 93  64
                         if (timeoutAsString.contains(COLON)) {
 94  44
                                 String[] timeoutByPath = timeoutAsString.split(COLON);
 95  44
                                 pathPattern = timeoutByPath[0];
 96  44
                                 timeout = timeoutByPath[1];
 97  44
                         } else {
 98  20
                                 isDefault = true;
 99  20
                                 timeout = timeoutAsString;
 100  
                         }
 101  64
                 }
 102  
 
 103  
                 public boolean allowedByPath(String path) {
 104  56
                         if (path != null) {
 105  50
                                 return path.matches(regexOf(pathPattern));
 106  
                         }
 107  6
                         return false;
 108  
                 }
 109  
 
 110  
                 public boolean isDefault() {
 111  22
                         return isDefault;
 112  
                 }
 113  
 
 114  
                 public String getPathPattern() {
 115  64
                         return pathPattern;
 116  
                 }
 117  
 
 118  
                 public long getTimeoutInSecs() {
 119  39
                         for (TimeoutParser parser : parsers) {
 120  72
                                 if (parser.isValid(timeout)) {
 121  37
                                         return parser.asSeconds(timeout);
 122  
                                 }
 123  35
                         }
 124  2
                         throw new TimeoutFormatException("No format found for timeout: "+timeout);
 125  
                 }
 126  
 
 127  
                 public String getTimeoutAsString() {
 128  0
                         return timeoutAsString;
 129  
                 }
 130  
 
 131  
                 private String regexOf(String pattern) {
 132  
                         try {
 133  
                                 // check if pattern is already a valid regex
 134  50
                                 Pattern.compile(pattern);
 135  19
                                 return pattern;
 136  31
                         } catch (PatternSyntaxException e) {
 137  
                                 // assume Ant-style pattern: **/path/*.story
 138  31
                                 return pattern.replace("*", ".*");
 139  
                         }
 140  
                 }
 141  
 
 142  
                 @Override
 143  
                 public String toString() {
 144  0
                         return ToStringBuilder.reflectionToString(this,
 145  
                                         ToStringStyle.SHORT_PREFIX_STYLE);
 146  
                 }
 147  
         }
 148  
 
 149  
         public static interface TimeoutParser {
 150  
 
 151  
                 boolean isValid(String timeout);
 152  
 
 153  
                 long asSeconds(String timeout);
 154  
 
 155  
         }
 156  
 
 157  
         /**
 158  
          * A simple parser for timeouts of format: 1d 2h 30m 15s.
 159  
          */
 160  
         public static class SimpleTimeoutParser implements TimeoutParser {
 161  
 
 162  
                 private static final String UNIT_PATTERN = "[a-zA-Z]+";
 163  1
                 private static final Pattern TIMEOUT_PATTERN = compile("(\\d+)\\s*("
 164  
                                 + UNIT_PATTERN + ")");
 165  40
                 private Map<String, Long> units = new HashMap<String, Long>();
 166  
 
 167  40
                 public SimpleTimeoutParser() {
 168  40
                         addUnit("d", 24 * 3600).addUnit("h", 3600).addUnit("m", 60)
 169  40
                                         .addUnit("s", 1);
 170  40
                 }
 171  
 
 172  
                 private SimpleTimeoutParser addUnit(String unit, long value) {
 173  160
                         if (!unit.matches(UNIT_PATTERN)) {
 174  0
                                 throw new TimeoutFormatException("Unit '" + unit
 175  
                                                 + "' must be a non-numeric word");
 176  
                         }
 177  160
                         if (value < 0) {
 178  0
                                 throw new TimeoutFormatException("Unit value '" + value
 179  
                                                 + "' cannot be negative");
 180  
                         }
 181  160
                         units.put(unit, Long.valueOf(value));
 182  160
                         return this;
 183  
                 }
 184  
 
 185  
                 public boolean isValid(String timeout) {
 186  38
                         return TIMEOUT_PATTERN.matcher(timeout).find();
 187  
                 }
 188  
 
 189  
                 public long asSeconds(String timeout) {
 190  5
                         long total = 0;
 191  5
                         Matcher matcher = TIMEOUT_PATTERN.matcher(timeout);
 192  16
                         while (matcher.find()) {
 193  11
                                 long value = Long.parseLong(matcher.group(1));
 194  11
                                 String unit = matcher.group(2);
 195  11
                                 if (!units.containsKey(unit)) {
 196  0
                                         throw new TimeoutFormatException("Unrecognized unit: "
 197  
                                                         + unit);
 198  
                                 }
 199  11
                                 total += units.get(unit).longValue() * value;
 200  11
                         }
 201  5
                         return total;
 202  
                 }
 203  
 
 204  
         }
 205  
 
 206  
         /**
 207  
          * A digit parser for timeouts
 208  
          */
 209  40
         public static class DigitTimeoutParser implements TimeoutParser {
 210  
 
 211  1
                 private static final Pattern TIMEOUT_PATTERN = compile("(\\d+)");
 212  
 
 213  
                 public boolean isValid(String timeout) {
 214  33
                         return TIMEOUT_PATTERN.matcher(timeout).find();
 215  
                 }
 216  
 
 217  
                 public long asSeconds(String timeout) {
 218  31
                         return Long.parseLong(timeout);
 219  
                 }
 220  
 
 221  
         }
 222  
 
 223  
         @SuppressWarnings("serial")
 224  
         public static class TimeoutFormatException extends IllegalArgumentException {
 225  
 
 226  
                 public TimeoutFormatException(String message) {
 227  2
                         super(message);
 228  2
                 }
 229  
 
 230  
         }
 231  
 }