/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Business Objects nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Autoburn_Test.java
* Created: Jan 14, 2005
* By: Peter Cardwell
*/
package org.openquark.gems.client;
import java.util.Arrays;
import java.util.HashSet;
import junit.extensions.TestSetup;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.openquark.cal.CALPlatformTestModuleNames;
import org.openquark.cal.compiler.CodeAnalyser;
import org.openquark.cal.compiler.MessageLogger;
import org.openquark.cal.compiler.ModuleName;
import org.openquark.cal.compiler.TypeException;
import org.openquark.cal.compiler.TypeExpr;
import org.openquark.cal.module.Cal.Collections.CAL_List;
import org.openquark.cal.module.Cal.Core.CAL_Prelude;
import org.openquark.cal.services.BasicCALServices;
import org.openquark.cal.services.GemEntity;
import org.openquark.gems.client.AutoburnLogic.AutoburnInfo;
/**
* Tests various uses of AutoburnLogic.getAutoburnInfo().
* @author Peter Cardwell
*/
public class Autoburn_Test extends TestCase {
static BasicCALServices calServices;
private static final ModuleName testModule = CALPlatformTestModuleNames.M2;
public Autoburn_Test(String name) {
super(name);
}
public Autoburn_Test() {
super("");
}
/**
* Tests the version of getAutoburnInfo() that takes an array of TypeExpr objects.
*/
public void testSimpleAutoburn() {
// Make the source a function of type a -> b -> c, and the destination a gem of type a -> b
TypeExpr[] sourceTypes = new TypeExpr[] {
TypeExpr.makeParametricType(), // first argument
TypeExpr.makeParametricType(), // second argument
TypeExpr.makeParametricType() // output type
};
TypeExpr destType = calServices.getTypeFromString(testModule, "a -> b");
AutoburnInfo autoburnInfo = AutoburnLogic.getAutoburnInfo(destType, sourceTypes, calServices.getTypeCheckInfo(testModule));
assertEquals(AutoburnLogic.AutoburnUnifyStatus.AMBIGUOUS_NOT_NECESSARY, autoburnInfo.getAutoburnUnifyStatus());
// Every possible burn combination is valid here, ie leaving both arguments unburned,
// burning either argument, or burning both arguments.
assertEquals(3, autoburnInfo.getBurnCombinations().size());
int[] argsToBurn = (autoburnInfo.getBurnCombinations().get(0)).getInputsToBurn();
assertTrue(Arrays.equals(argsToBurn, new int[] {0}));
argsToBurn = (autoburnInfo.getBurnCombinations().get(1)).getInputsToBurn();
assertTrue(Arrays.equals(argsToBurn, new int[] {1}));
argsToBurn = (autoburnInfo.getBurnCombinations().get(2)).getInputsToBurn();
assertTrue(Arrays.equals(argsToBurn, new int[] {0, 1}));
}
/**
* Tests the version of getAutoburnInfo() that takes an array of TypeExpr objects,
* in the case where burning should not be necessary.
*/
public void testSimpleAutoburnNotNecessary() {
// Make the source a function of type a -> b, and the destination a gem of type [a].
TypeExpr[] sourceTypes = new TypeExpr[] {
TypeExpr.makeParametricType(), // second argument
TypeExpr.makeParametricType() // output type
};
TypeExpr destType = calServices.getTypeFromString(testModule, "[a]");
AutoburnInfo autoburnInfo = AutoburnLogic.getAutoburnInfo(destType, sourceTypes, calServices.getTypeCheckInfo(testModule));
// The only possibility here is to leave the argument unburned.
assertEquals(AutoburnLogic.AutoburnUnifyStatus.NOT_NECESSARY, autoburnInfo.getAutoburnUnifyStatus());
assertEquals(0, autoburnInfo.getBurnCombinations().size());
}
/**
* Tests the version of getAutoburnInfo() that takes a gem entity.
*/
public void testAutoburnGemEntity() {
// We'll use List.map for the source entity and [a] -> [b] for the destination
GemEntity gemEntity = calServices.getCALWorkspace().getGemEntity(CAL_List.Functions.map);
TypeExpr destType = calServices.getTypeFromString(testModule, "[a] -> [b]");
AutoburnInfo autoburnInfo = AutoburnLogic.getAutoburnInfo(destType, gemEntity, calServices.getTypeCheckInfo(testModule));
assertEquals(AutoburnLogic.AutoburnUnifyStatus.UNAMBIGUOUS, autoburnInfo.getAutoburnUnifyStatus());
// The only way to connect these two types together is by burning the second argument.
assertEquals(1, autoburnInfo.getBurnCombinations().size());
int[] argsToBurn = (autoburnInfo.getBurnCombinations().get(0)).getInputsToBurn();
assertTrue(Arrays.equals(argsToBurn, new int[] {1}));
}
/**
* Tests getAutoburnInfo() with a two-argument gem where both of the arguments are unburnt.
*/
public void testAutoburnDualArgGemBothArgsUnburnt() {
CodeAnalyser codeAnalyser = new CodeAnalyser(calServices.getTypeChecker(),
calServices.getCALWorkspace().getMetaModule(testModule).getTypeInfo(),
true,
false);
// Create the source gem
// Output type: Boolean, Input types: Double, Boolean
String code = "let x = (a :: " + CAL_Prelude.TypeConstructors.Double.getQualifiedName() + ", b :: " + CAL_Prelude.TypeConstructors.Boolean.getQualifiedName() + "); in " + CAL_Prelude.DataConstructors.True.getQualifiedName();
Gem sourceGem = new CodeGem(codeAnalyser, code, new HashSet<String>());
// Create the destination gem
// Input type: Double -> Boolean -> a, Output type: a
code = "f 1.0 " + CAL_Prelude.DataConstructors.True.getQualifiedName();
Gem destGem = new CodeGem(codeAnalyser, code, new HashSet<String>());
// It should be necessary to burn both inputs of the source gem
AutoburnLogic.AutoburnInfo autoburnInfo = AutoburnLogic.getAutoburnInfo(destGem.getInputPart(0).getType(), sourceGem, calServices.getTypeCheckInfo(testModule));
assertEquals(AutoburnLogic.AutoburnUnifyStatus.UNAMBIGUOUS, autoburnInfo.getAutoburnUnifyStatus());
assertEquals(1, autoburnInfo.getBurnCombinations().size());
int[] argsToBurn = (autoburnInfo.getBurnCombinations().get(0)).getInputsToBurn();
assertTrue(Arrays.equals(new int[] {0, 1}, argsToBurn));
}
/**
* Tests getAutoburnInfo() with a two-argument gem where the first argument is pre-burnt.
*/
public void testAutoburnDualArgGemFirstArgBurnt() {
CodeAnalyser codeAnalyser = new CodeAnalyser(calServices.getTypeChecker(),
calServices.getCALWorkspace().getMetaModule(testModule).getTypeInfo(),
true,
false);
GemGraph gemGraph = new GemGraph();
// Create the source gem
// Output type: Boolean, Input types: Double, Boolean
String code = "let x = (a :: " + CAL_Prelude.TypeConstructors.Double.getQualifiedName() + ", b :: " + CAL_Prelude.TypeConstructors.Boolean.getQualifiedName() + "); in " + CAL_Prelude.DataConstructors.True.getQualifiedName();
Gem sourceGem = new CodeGem(codeAnalyser, code, new HashSet<String>());
gemGraph.addGem(sourceGem);
// Create the destination gem
// Input type: Double -> Boolean -> a, Output type: a
code = "f 1.0 " + CAL_Prelude.DataConstructors.True.getQualifiedName();
Gem destGem = new CodeGem(codeAnalyser, code, new HashSet<String>());
gemGraph.addGem(destGem);
// Now burn the first input of the gem
sourceGem.getInputPart(0).setBurnt(true);
try {
gemGraph.typeGemGraph(calServices.getTypeCheckInfo(testModule));
} catch (TypeException e) {
throw new Error("Error typing gem graph");
}
// It should be necessary to burn both inputs of the source gem
AutoburnLogic.AutoburnInfo autoburnInfo = AutoburnLogic.getAutoburnInfo(destGem.getInputPart(0).getType(), sourceGem, calServices.getTypeCheckInfo(testModule));
assertEquals(AutoburnLogic.AutoburnUnifyStatus.UNAMBIGUOUS, autoburnInfo.getAutoburnUnifyStatus());
assertEquals(1, autoburnInfo.getBurnCombinations().size());
int[] argsToBurn = (autoburnInfo.getBurnCombinations().get(0)).getInputsToBurn();
assertTrue(Arrays.equals(new int[] {1}, argsToBurn));
}
/**
* Tests getAutoburnInfo() with a two-argument gem where the second argument is pre-burnt.
*/
public void testAutoburnDualArgGemSecondArgBurnt() {
CodeAnalyser codeAnalyser = new CodeAnalyser(calServices.getTypeChecker(),
calServices.getCALWorkspace().getMetaModule(testModule).getTypeInfo(),
true,
false);
GemGraph gemGraph = new GemGraph();
// Create the source gem
// Output type: Boolean, Input types: Double, Boolean
String code = "let x = (a :: " + CAL_Prelude.TypeConstructors.Double.getQualifiedName() + ", b :: " + CAL_Prelude.TypeConstructors.Boolean.getQualifiedName() + "); in " + CAL_Prelude.DataConstructors.True.getQualifiedName();
Gem sourceGem = new CodeGem(codeAnalyser, code, new HashSet<String>());
gemGraph.addGem(sourceGem);
// Create the destination gem
// Input type: Double -> Boolean -> a, Output type: a
code = "f 1.0 " + CAL_Prelude.DataConstructors.True.getQualifiedName();
Gem destGem = new CodeGem(codeAnalyser, code, new HashSet<String>());
gemGraph.addGem(destGem);
// Now burn the first input of the gem
sourceGem.getInputPart(1).setBurnt(true);
try {
gemGraph.typeGemGraph(calServices.getTypeCheckInfo(testModule));
} catch (TypeException e) {
throw new Error("Error typing gem graph");
}
// It should be necessary to burn both inputs of the source gem
AutoburnLogic.AutoburnInfo autoburnInfo = AutoburnLogic.getAutoburnInfo(destGem.getInputPart(0).getType(), sourceGem, calServices.getTypeCheckInfo(testModule));
assertEquals(AutoburnLogic.AutoburnUnifyStatus.UNAMBIGUOUS, autoburnInfo.getAutoburnUnifyStatus());
assertEquals(1, autoburnInfo.getBurnCombinations().size());
int[] argsToBurn = (autoburnInfo.getBurnCombinations().get(0)).getInputsToBurn();
assertTrue(Arrays.equals(new int[] {0}, argsToBurn));
}
/**
* Tests getAutoburnInfo() with a two-argument gem where both arguments are pre-burnt.
*/
public void testAutoburnDualArgGemBothArgsBurnt() {
CodeAnalyser codeAnalyser = new CodeAnalyser(calServices.getTypeChecker(),
calServices.getCALWorkspace().getMetaModule(testModule).getTypeInfo(),
true,
false);
GemGraph gemGraph = new GemGraph();
// Create the source gem
// Output type: Boolean, Input types: Double, Boolean
String code = "let x = (a :: " + CAL_Prelude.TypeConstructors.Double.getQualifiedName() + ", b :: " + CAL_Prelude.TypeConstructors.Boolean.getQualifiedName() + "); in " + CAL_Prelude.DataConstructors.True.getQualifiedName();
Gem sourceGem = new CodeGem(codeAnalyser, code, new HashSet<String>());
gemGraph.addGem(sourceGem);
// Create the destination gem
// Input type: Double -> Boolean -> a, Output type: a
code = "f 1.0 " + CAL_Prelude.DataConstructors.True.getQualifiedName();
Gem destGem = new CodeGem(codeAnalyser, code, new HashSet<String>());
gemGraph.addGem(destGem);
// Now burn both inputs of the gem
sourceGem.getInputPart(0).setBurnt(true);
sourceGem.getInputPart(1).setBurnt(true);
try {
gemGraph.typeGemGraph(calServices.getTypeCheckInfo(testModule));
} catch (TypeException e) {
throw new Error("Error typing gem graph");
}
// It should be necessary to burn both inputs of the source gem
AutoburnLogic.AutoburnInfo autoburnInfo = AutoburnLogic.getAutoburnInfo(destGem.getInputPart(0).getType(), sourceGem, calServices.getTypeCheckInfo(testModule));
assertEquals(AutoburnLogic.AutoburnUnifyStatus.NOT_NECESSARY, autoburnInfo.getAutoburnUnifyStatus());
assertEquals(0, autoburnInfo.getBurnCombinations().size());
}
/**
* Test autoburning of a gem that has a bound connection.
*/
public void testAutoburnBoundGem() {
// Create a new code analyser so we can build code gems
CodeAnalyser codeAnalyser = new CodeAnalyser(calServices.getTypeChecker(),
calServices.getCALWorkspace().getMetaModule(testModule).getTypeInfo(),
true,
false);
// Create the code gem that will be bound to the primary source gem.
String code = "(firstSecondaryArg, secondSecondaryArg)";
Gem secondaryCodeGem = new CodeGem(codeAnalyser, code, new HashSet<String>());
// Create the code gem that will act as the source gem for the autoburn query.
code = "(firstPrimaryArg::" + CAL_Prelude.TypeConstructors.Double.getQualifiedName() + ", secondPrimaryArg, thirdPrimaryArg::" + CAL_Prelude.TypeConstructors.Int.getQualifiedName() + ", fourth::" + CAL_Prelude.TypeConstructors.Boolean.getQualifiedName() + ", fifth::" + CAL_Prelude.TypeConstructors.Boolean.getQualifiedName() + ")";
CodeGem primaryCodeGem = new CodeGem(codeAnalyser, code, new HashSet<String>());
// Create the code gem that will act as the destination gem for the autoburn query.
code = "f 1.0 " + CAL_Prelude.DataConstructors.True.getQualifiedName() + " " + CAL_Prelude.DataConstructors.True.getQualifiedName();
Gem destinationCodeGem = new CodeGem(codeAnalyser, code, new HashSet<String>());
// Connect secondaryCodeGem to first input of primaryCodeGem
Connection connection = new Connection(secondaryCodeGem.getOutputPart(), primaryCodeGem.getInputPart(1));
primaryCodeGem.getInputPart(1).bindConnection(connection);
secondaryCodeGem.getOutputPart().bindConnection(connection);
AutoburnLogic.AutoburnInfo autoburnInfo = AutoburnLogic.getAutoburnInfo(destinationCodeGem.getInputPart(0).getType(), primaryCodeGem, calServices.getTypeCheckInfo(testModule));
assertEquals(AutoburnLogic.AutoburnUnifyStatus.UNAMBIGUOUS, autoburnInfo.getAutoburnUnifyStatus());
assertEquals(1, autoburnInfo.getBurnCombinations().size());
int[] argsToBurn = (autoburnInfo.getBurnCombinations().get(0)).getInputsToBurn();
assertTrue(Arrays.equals(new int[] {0, 3, 4}, argsToBurn));
}
/**
* Test autoburning of a gem that has a bound connection and one of it's inputs already burnt.
*/
public void testAutoburnBoundAndBurntGem() {
//Create a new code analyser so we can build code gems
CodeAnalyser codeAnalyser = new CodeAnalyser(calServices.getTypeChecker(),
calServices.getCALWorkspace().getMetaModule(testModule).getTypeInfo(),
true,
false);
GemGraph gemGraph = new GemGraph();
String code = "(firstSecondaryArg, secondSecondaryArg)";
Gem secondaryCodeGem = new CodeGem(codeAnalyser, code, new HashSet<String>());
gemGraph.addGem(secondaryCodeGem);
code = "(firstPrimaryArg::" + CAL_Prelude.TypeConstructors.Double.getQualifiedName() + ", secondPrimaryArg, thirdPrimaryArg::" + CAL_Prelude.TypeConstructors.Int.getQualifiedName() + ", fourth::" + CAL_Prelude.TypeConstructors.Boolean.getQualifiedName() + ", fifth::" + CAL_Prelude.TypeConstructors.Boolean.getQualifiedName() + ")";
CodeGem primaryCodeGem = new CodeGem(codeAnalyser, code, new HashSet<String>());
gemGraph.addGem(primaryCodeGem);
code = "f 1.0 " + CAL_Prelude.DataConstructors.True.getQualifiedName() + " " + CAL_Prelude.DataConstructors.True.getQualifiedName();
Gem outputCodeGem = new CodeGem(codeAnalyser, code, new HashSet<String>());
gemGraph.addGem(outputCodeGem);
// Connect secondaryCodeGem to second input of primaryCodeGem
Connection connection = new Connection(secondaryCodeGem.getOutputPart(), primaryCodeGem.getInputPart(1));
primaryCodeGem.getInputPart(1).bindConnection(connection);
secondaryCodeGem.getOutputPart().bindConnection(connection);
// Burn the fourth input to the primary code gem, and re-set the output type appropriately
primaryCodeGem.getInputPart(3).setBurnt(true);
try {
gemGraph.typeGemGraph(calServices.getTypeCheckInfo(testModule));
} catch (TypeException e) {
throw new Error("Error typing gem graph");
}
AutoburnLogic.AutoburnInfo autoburnInfo = AutoburnLogic.getAutoburnInfo(outputCodeGem.getInputPart(0).getType(), primaryCodeGem, calServices.getTypeCheckInfo(testModule));
assertEquals(AutoburnLogic.AutoburnUnifyStatus.UNAMBIGUOUS, autoburnInfo.getAutoburnUnifyStatus());
assertEquals(1, autoburnInfo.getBurnCombinations().size());
int[] argsToBurn = (autoburnInfo.getBurnCombinations().get(0)).getInputsToBurn();
assertTrue(Arrays.equals(new int[] {0, 4}, argsToBurn));
}
/**
* Tests autoburning on a code gem that outputs a function even before any arguments are burnt.
*/
public void testAutoburnFunctionGem() {
// Create a new code analyzer so we can build code gems
CodeAnalyser codeAnalyser = new CodeAnalyser(calServices.getTypeChecker(),
calServices.getCALWorkspace().getMetaModule(testModule).getTypeInfo(),
true,
false);
// Create a gem whose output type is Num a => [a] -> [a]
String code = CAL_List.Functions.map.getQualifiedName() + " (\\ x -> x + 1)";
Gem functionCodeGem = new CodeGem(codeAnalyser, code, new HashSet<String>());
// Create another gem whose input is of type Num c => [c] -> b
code = "f [1, 2]";
Gem destinationCodeGem = new CodeGem(codeAnalyser, code, new HashSet<String>());
// These gems should be able to connect without having to do any autoburning
AutoburnLogic.AutoburnInfo autoburnInfo = AutoburnLogic.getAutoburnInfo(destinationCodeGem.getInputPart(0).getType(), functionCodeGem, calServices.getTypeCheckInfo(testModule));
assertEquals(AutoburnLogic.AutoburnUnifyStatus.NOT_NECESSARY, autoburnInfo.getAutoburnUnifyStatus());
assertEquals(autoburnInfo.getBurnCombinations().size(), 0);
}
/**
* Second test autoburning a code gem that outputs a function even before any arguments are burnt.
* In this case, the test is designed so that burning the input is necessary.
*/
public void testAutoburnFunctionGem2() {
// Create a new code analyser so we can build code gems
CodeAnalyser codeAnalyser = new CodeAnalyser(calServices.getTypeChecker(),
calServices.getCALWorkspace().getMetaModule(testModule).getTypeInfo(),
true,
false);
// Create a gem with output type Num b => [b] -> [b] and input type a
String code = "let y = a; in " + CAL_List.Functions.map.getQualifiedName() + " (\\x -> x + 1)";
Gem functionCodeGem = new CodeGem(codeAnalyser, code, new HashSet<String>());
// Create a gem whose first input is of type Num e => c -> [e] -> d
code = "f b [1, 2]";
Gem destinationCodeGem = new CodeGem(codeAnalyser, code, new HashSet<String>());
// In order to connect these gems, we would need to burn the first input. Let's make sure the autoBurner agrees.
AutoburnLogic.AutoburnInfo autoburnInfo = AutoburnLogic.getAutoburnInfo(destinationCodeGem.getInputPart(0).getType(), functionCodeGem, calServices.getTypeCheckInfo(testModule));
assertEquals(autoburnInfo.getAutoburnUnifyStatus(), AutoburnLogic.AutoburnUnifyStatus.UNAMBIGUOUS);
assertEquals(autoburnInfo.getBurnCombinations().size(), 1);
int[] argsToBurn = (autoburnInfo.getBurnCombinations().get(0)).getInputsToBurn();
assertTrue(Arrays.equals(argsToBurn, new int[] {0}));
}
public static Test suite() {
TestSuite suite = new TestSuite(Autoburn_Test.class);
return new TestSetup(suite) {
@Override
protected void setUp() {
oneTimeSetUp();
}
@Override
protected void tearDown() {
oneTimeTearDown();
}
};
}
public static void oneTimeSetUp() {
calServices = BasicCALServices.make(GemCutter.GEMCUTTER_PROP_WORKSPACE_FILE, "cal.platform.test.cws", null);
calServices.compileWorkspace(null, new MessageLogger());
}
public static void oneTimeTearDown() {
calServices = null;
}
}