/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.junit; import java.util.LinkedList; import java.util.List; public final class ParameterizationBuilder { private final List<Parameterization> list; /** * Creates a builder with no starting parameterizations. */ public ParameterizationBuilder() { this(new LinkedList<Parameterization>()); } /** * Creates a builder backed by a list. Changes to the one will be reflected in the other, and {@linkplain #asList()} * will return the same instance as is passed to this method. * @param backingList The list that backs this builder. */ public ParameterizationBuilder(List<Parameterization> backingList) { list = backingList; } /** * Adds a parameterization to the end of the list. * @param name the parameterization's name * @param args the parameterization's arguments */ public void add(String name, Object... args) { list.add(Parameterization.create(name, args)); } /** * Adds a parameterization that's expected to fail to the end of the list * @param name the parameterization's name * @param args the parameterization's arguments */ public void addFailing(String name, Object... args) { list.add(Parameterization.failing(name, args)); } /** * Adds a parameterization to the end of the list. * @param name the parameterization's name * @param expectedToPass whether the parameterization is expected to pass any tests * @param args the parameterization's arguments */ public void create(String name, boolean expectedToPass, Object... args) { list.add(new Parameterization(name, expectedToPass, args)); } /** * <p>Performs a cartesian product of parameterizations by prepending each of the incoming args to each * existing parameter's arg list.</p> * * For instance, if you have parameters <tt>[ ("A", 1, 2, 3) and ("B", 4, 5, 6) ]</tt>, and you called * <tt>multiplyParametersByPrepending("foo-", 'c', "bar", 'd')</tt>, you would get the following * parameterizations, in this order: * <ol> * <li>"foo-A", 'c', 1, 2, 3</li> * <li>"bar-A", 'd', 1, 2, 3</li> * <li>"foo-B", 'c', 4, 5, 6</li> * <li>"bar-B", 'd', 1, 2, 3</li> * </ol> * @param args the list of named args to multiply by. Every other element (starting with the first) in the list must * be a String that represents the name prefix; the element following each prefix is the argument to append to * the args list. * @throws IllegalArgumentException if the args aren't given as String-Object pairings. * @throws IllegalStateException if there are no existing parameterizations to multiply by */ public void multiplyParametersByPrepending(Object... args) { cartesianProduct(false, args); } /** * <p>Performs a cartesian product of parameterizations by appending each of the incoming args to each * existing parameter's arg list. If you supply a prefix, it will also be appended to each parameter's name.</p> * * <p>This works just like {@link #multiplyParametersByPrepending(Object...)}, only each parameter label and * value is appended instead of prepended.</p> * @param args the list of named args to multiply by. Every other element (starting with the first) in the list must * be a String that represents the name suffix; the element following each suffix is the argument to append to * the args list. * @throws IllegalArgumentException if the args aren't given as String-Object pairings. * @throws IllegalStateException if there are no existing parameterizations to multiply by * @see #multiplyParametersByPrepending(Object...) */ public void multiplyParametersByAppending(Object... args) { cartesianProduct(true, args); } private void cartesianProduct(boolean append, Object[] args) { boolean usingAsserts = true; //noinspection AssertWithSideEffects assert (usingAsserts=true); // side effect is okay in this usage final Object PLACEHOLDER = usingAsserts ? new Object() : null; if (list.size() == 0) { throw new IllegalStateException("can't multiply yet -- no args defined"); } if ( (args.length % 2) != 0) { throw new IllegalArgumentException("odd number of arguments"); } List<Parameterization> product = new LinkedList<>(); for (Parameterization param : list) { Object[] origArgs = param.getArguments(); Object[] newArgs = new Object[origArgs.length + 1]; if (usingAsserts) { for (int i=0; i < newArgs.length; ++i) { newArgs[i] = PLACEHOLDER; } } System.arraycopy(origArgs, 0, newArgs, append ? 0 : 1, origArgs.length); for(int i=0; i < args.length; i+= 2) { String label; try { label = (String)args[i]; } catch (ClassCastException e) { throw new IllegalArgumentException("argument at index " + i + " is not a String"); } if (label == null) { label = param.getName(); } else { label = append ? param.getName() + label : label + param.getName(); } newArgs[append ? origArgs.length : 0] = args[i+1]; if (usingAsserts) { for (Object newArg : newArgs) assert newArg != PLACEHOLDER; } product.add(new Parameterization(label, param.expectedToPass(), newArgs)); } } list.clear(); list.addAll(product); } /** * Represents this builder's parameterizations. The returning list is backed by this builder, so changes * to either are seen in both. * @return the list that backs this builder */ public List<Parameterization> asList() { return list; } }