| package org.eclipse.cdt.debug.core.tests; |
| |
| /* |
| * (c) Copyright QNX Software Systems Ltd. 2002. |
| * All Rights Reserved. |
| */ |
| |
| |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.lang.reflect.InvocationTargetException; |
| |
| import junit.extensions.TestSetup; |
| import junit.framework.Test; |
| import junit.framework.TestCase; |
| import junit.framework.TestSuite; |
| |
| import org.eclipse.cdt.core.model.ICProject; |
| import org.eclipse.cdt.debug.core.cdi.CDIException; |
| import org.eclipse.cdt.debug.core.cdi.ICDIBreakpointManager; |
| import org.eclipse.cdt.debug.core.cdi.ICDICondition; |
| import org.eclipse.cdt.debug.core.cdi.ICDILocation; |
| import org.eclipse.cdt.debug.core.cdi.ICDISession; |
| import org.eclipse.cdt.debug.core.cdi.model.ICDIBreakpoint; |
| import org.eclipse.cdt.debug.core.cdi.model.ICDILocationBreakpoint; |
| import org.eclipse.cdt.debug.core.cdi.model.ICDITarget; |
| import org.eclipse.cdt.debug.mi.core.MIException; |
| import org.eclipse.cdt.debug.testplugin.CDebugHelper; |
| import org.eclipse.cdt.debug.testplugin.CProjectHelper; |
| import org.eclipse.core.resources.IWorkspace; |
| import org.eclipse.core.resources.IWorkspaceRoot; |
| import org.eclipse.core.resources.IncrementalProjectBuilder; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.Path; |
| |
| /** |
| * @author Peter Graves |
| * |
| * This file contains a set of generic tests for the CDI break point interfaces. |
| * It will currenly use the mi implementation. |
| * |
| */ |
| public class BreakpointTests extends TestCase { |
| IWorkspace workspace; |
| IWorkspaceRoot root; |
| static ICProject testProject=null; |
| NullProgressMonitor monitor; |
| static ICDISession session=null; |
| static ICDITarget targets[] = null; |
| |
| |
| /** |
| * Constructor for BreakpointTests |
| * @param name |
| */ |
| public BreakpointTests(String name) { |
| super(name); |
| /*** |
| * The tests assume that they have a working workspace |
| * and workspace root object to use to create projects/files in, |
| * so we need to get them setup first. |
| */ |
| workspace= ResourcesPlugin.getWorkspace(); |
| root= workspace.getRoot(); |
| monitor = new NullProgressMonitor(); |
| if (workspace==null) |
| fail("Workspace was not setup"); |
| if (root==null) |
| fail("Workspace root was not setup"); |
| |
| } |
| |
| /** |
| * Sets up the test fixture. |
| * |
| * Called before every test case method. |
| * |
| * Example code test the packages in the project |
| * "com.qnx.tools.ide.cdt.core" |
| */ |
| protected static void oneTimeSetUp() throws CoreException, InvocationTargetException, IOException { |
| ResourcesPlugin.getWorkspace().getDescription().setAutoBuilding(false); |
| /*** |
| * Create a new project and import the test source. |
| */ |
| String pluginRoot=org.eclipse.core.runtime.Platform.getPlugin("org.eclipse.cdt.debug.ui.tests").find(new Path("/")).getFile(); |
| pluginRoot=pluginRoot+"resources/debugTest.zip"; |
| testProject=CProjectHelper.createCProjectWithImport("filetest", pluginRoot); |
| if (testProject==null) |
| fail("Unable to create project"); |
| /* Build the test project.. */ |
| |
| testProject.getProject().build(IncrementalProjectBuilder.FULL_BUILD, null); |
| } |
| /** |
| * Tears down the test fixture. |
| * |
| * Called after every test case method. |
| */ |
| protected void tearDown() throws CoreException { |
| if (targets!=null) { |
| try { |
| targets[0].terminate(); |
| targets=null; |
| } catch (CDIException e) {} |
| } |
| if (session!=null) { |
| try { |
| session.terminate(); |
| session=null; |
| } catch (CDIException e) {} |
| } |
| } |
| |
| /** |
| * Tears down the test fixture. |
| * |
| * Called after every test case method. |
| */ |
| protected static void oneTimeTearDown() throws CoreException { |
| if (targets!=null) { |
| try { |
| targets[0].terminate(); |
| } catch (CDIException e) {} |
| } |
| if (session!=null) { |
| try { |
| session.terminate(); |
| } catch (CDIException e) {} |
| } |
| CProjectHelper.delete(testProject); |
| |
| } |
| |
| public static Test suite() { |
| TestSuite suite= new TestSuite(BreakpointTests.class); |
| /*** |
| * Create a wrapper suite around the test suite we created above to |
| * allow us to only do the general setup once for all the tests. This |
| * is needed because the creation of the source and target projects |
| * takes a long time and we really only need to do it once. We could |
| * do the setup in the constructor, but we need to be able to remove |
| * everything when we are done. |
| */ |
| TestSetup wrapper = new TestSetup(suite) { |
| |
| protected void setUp() throws FileNotFoundException, IOException, InterruptedException, InvocationTargetException, CoreException { |
| oneTimeSetUp(); |
| } |
| |
| protected void tearDown() throws FileNotFoundException, IOException, CoreException { |
| oneTimeTearDown(); |
| } |
| }; |
| return(wrapper); |
| } |
| |
| |
| |
| /*** |
| * A couple tests to make sure setting breakpoints on functions works as |
| * expected. |
| */ |
| public void testFunctionBreak() throws CoreException, MIException, IOException, CDIException, InterruptedException { |
| ICDISession session; |
| ICDIBreakpointManager breaks; |
| ICDILocation location; |
| boolean caught=false; |
| session=CDebugHelper.createSession("main", testProject); |
| assertNotNull(session); |
| breaks=session.getBreakpointManager(); |
| assertNotNull(breaks); |
| |
| /********************************************************************** |
| * Create a break point on a generic function |
| **********************************************************************/ |
| |
| location=breaks.createLocation(null, "func1", 0); |
| assertNotNull(location); |
| breaks.setLocationBreakpoint(0, location, null, null); |
| |
| /********************************************************************** |
| * Create a break point on main |
| **********************************************************************/ |
| |
| location=breaks.createLocation(null, "main", 0); |
| assertNotNull(location); |
| breaks.setLocationBreakpoint(0, location, null, null); |
| |
| |
| /********************************************************************** |
| * Try to create a break point on a function name that does not exist |
| * We expect that this will cause the setLocationBreakpoint to throw |
| * a CDIException |
| **********************************************************************/ |
| |
| location=breaks.createLocation(null, "badname", 0); |
| assertNotNull(location); |
| try { |
| breaks.setLocationBreakpoint(0, location, null, null); |
| } catch (CDIException e) { |
| caught=true; |
| } |
| assertTrue(caught); |
| |
| breaks.deleteAllBreakpoints(); |
| |
| /********************************************************************** |
| * Create a break point on a generic function and see if it will |
| * get hit and stop program execution. |
| **********************************************************************/ |
| |
| location=breaks.createLocation(null, "func1", 0); |
| assertNotNull(location); |
| breaks.setLocationBreakpoint(0, location, null, null); |
| targets=session.getTargets(); |
| /* We better only have one target connected to this session or something |
| * is not right... |
| */ |
| assertTrue(targets.length==1); |
| /* Resume the target, this should cause it to run till it hits the |
| * breakpoint |
| */ |
| targets[0].resume(); |
| /** |
| * Give the process up to 10 seconds to become either terminated or |
| * suspended. It sould hit the breakponint almost immediatly so we |
| * should only sleep for max 100 ms |
| */ |
| for (int x=0;x<100;x++) { |
| if (targets[0].isTerminated() || targets[0].isSuspended()) |
| break; |
| Thread.sleep(100); |
| } |
| assertTrue(targets[0].isSuspended()); |
| location=targets[0].getCurrentThread().getStackFrames()[0].getLocation(); |
| assertTrue(location.getLineNumber()==6); |
| assertTrue(location.getFunction().equals("func1")); |
| assertTrue(location.getFile().equals("main.c")); |
| |
| /* clean up the session */ |
| targets[0].terminate(); |
| int x=0; |
| while ((!targets[0].isTerminated()) && (x<30)) { |
| Thread.sleep(100); |
| } |
| if (!targets[0].isTerminated()) |
| targets[0].terminate(); |
| session.terminate(); |
| session=null; |
| targets=null; |
| |
| |
| } |
| |
| /*** |
| * A couple tests to make sure setting breakpoints on line numbers works as |
| * expected. |
| */ |
| public void testLineBreak() throws CoreException, MIException, IOException, CDIException, InterruptedException { |
| ICDIBreakpointManager breaks; |
| ICDILocation location; |
| boolean caught=false; |
| session=CDebugHelper.createSession("main", testProject); |
| assertNotNull(session); |
| breaks=session.getBreakpointManager(); |
| assertNotNull(breaks); |
| |
| /********************************************************************** |
| * Create a break point in a generic function |
| **********************************************************************/ |
| location=breaks.createLocation("main.c", null, 7); |
| assertNotNull(location); |
| breaks.setLocationBreakpoint(0, location, null, null); |
| |
| |
| /********************************************************************** |
| * Create a break point in main |
| **********************************************************************/ |
| location=breaks.createLocation("main.c", null, 18); |
| assertNotNull(location); |
| breaks.setLocationBreakpoint(0, location, null, null); |
| |
| |
| /********************************************************************** |
| * Try to create a break point on a line that does not exist |
| * We expect that this will cause the setLocationBreakpoint to throw |
| * a CDIException |
| **********************************************************************/ |
| |
| location=breaks.createLocation("main.c", null, 30); |
| assertNotNull(location); |
| try { |
| breaks.setLocationBreakpoint(0, location, null, null); |
| } catch (CDIException e) { |
| caught=true; |
| } |
| assertTrue(caught); |
| |
| caught=false; |
| /********************************************************************** |
| * Try to create a break point on a line that does not have code on it |
| **********************************************************************/ |
| |
| location=breaks.createLocation("main.c", null, 11); |
| assertNotNull(location); |
| breaks.setLocationBreakpoint(0, location, null, null); |
| |
| /********************************************************************** |
| * Create a break point in a generic function without passing the source |
| * file name. At the time of writing this would just silently fail, so |
| * to make sure it works, we will do it once with a valid line number |
| * and once with an invalid line number, and the first should always |
| * succeed and the second should always throw an exception. |
| **********************************************************************/ |
| location=breaks.createLocation(null, null, 7); |
| assertNotNull(location); |
| breaks.setLocationBreakpoint(0, location, null, null); |
| caught=false; |
| location=breaks.createLocation(null, null, 30); |
| assertNotNull(location); |
| try { |
| breaks.setLocationBreakpoint(0, location, null, null); |
| } catch (CDIException e) { |
| caught=true; |
| } |
| assertTrue("Ignoring line numbers with no file specified?", caught); |
| |
| breaks.deleteAllBreakpoints(); |
| |
| /********************************************************************** |
| * Create a break point on a line number and see if it will |
| * get hit and stop program execution. |
| **********************************************************************/ |
| |
| location=breaks.createLocation(null, null, 7); |
| assertNotNull(location); |
| breaks.setLocationBreakpoint(0, location, null, null); |
| targets=session.getTargets(); |
| /* We better only have one target connected to this session or something |
| * is not right... |
| */ |
| assertTrue(targets.length==1); |
| /* Resume the target, this should cause it to run till it hits the |
| * breakpoint |
| */ |
| targets[0].resume(); |
| /** |
| * Give the process up to 10 seconds to become either terminated or |
| * suspended. It sould hit the breakponint almost immediatly so we |
| * should only sleep for max 100 ms |
| */ |
| for (int x=0;x<100;x++) { |
| if (targets[0].isSuspended() || targets[0].isTerminated()) |
| break; |
| Thread.sleep(100); |
| } |
| assertTrue("Suspended: " + targets[0].isSuspended() + " Termiunated: " + targets[0].isTerminated(), targets[0].isSuspended()); |
| location=targets[0].getCurrentThread().getStackFrames()[0].getLocation(); |
| assertTrue(location.getLineNumber()==7); |
| assertTrue(location.getFunction().equals("func1")); |
| assertTrue(location.getFile().equals("main.c")); |
| |
| |
| /* clean up the session */ |
| session.terminate(); |
| session=null; |
| targets=null; |
| |
| } |
| /*** |
| * A couple tests to make sure getting breakpoints works as expected |
| */ |
| public void testGetBreak() throws CoreException, MIException, IOException, CDIException { |
| ICDIBreakpointManager breaks; |
| ICDILocation location; |
| ICDIBreakpoint[] breakpoints; |
| ICDILocationBreakpoint curbreak; |
| session=CDebugHelper.createSession("main", testProject); |
| assertNotNull(session); |
| breaks=session.getBreakpointManager(); |
| assertNotNull(breaks); |
| |
| /********************************************************************** |
| * Make sure initially we don't have any breakpoints |
| **********************************************************************/ |
| breakpoints=breaks.getBreakpoints(); |
| assertNotNull(breakpoints); |
| assertTrue(breakpoints.length==0); |
| |
| /********************************************************************** |
| * Make sure if we create a simple breakpoint, that we can get it back |
| * from the system |
| *********************************************************************/ |
| /* Create a break point on a generic function */ |
| location=breaks.createLocation("main.c", "func1", 0); |
| assertNotNull(location); |
| breaks.setLocationBreakpoint(0, location, null, null); |
| |
| breakpoints=breaks.getBreakpoints(); |
| assertNotNull(breakpoints); |
| assertTrue(breakpoints.length==1); |
| if (breakpoints[0] instanceof ICDILocationBreakpoint) { |
| curbreak=(ICDILocationBreakpoint) breakpoints[0]; |
| } else |
| curbreak=null; |
| assertNotNull(curbreak); |
| |
| assertTrue(curbreak.getLocation().equals(location)); |
| |
| /********************************************************************** |
| * Make sure if we create multiple break points that we can still |
| * get them all back from the system, |
| **********************************************************************/ |
| /* Create another break point on main */ |
| location=breaks.createLocation("main.c", "main", 0); |
| assertNotNull(location); |
| breaks.setLocationBreakpoint(0, location, null, null); |
| |
| breakpoints=breaks.getBreakpoints(); |
| assertNotNull(breakpoints); |
| assertTrue(breakpoints.length==2); |
| if (breakpoints[1] instanceof ICDILocationBreakpoint) { |
| curbreak=(ICDILocationBreakpoint) breakpoints[1]; |
| } else |
| curbreak=null; |
| assertNotNull(curbreak); |
| /* Make sure the location still looks like we expect it to.. |
| . */ |
| assertTrue(curbreak.getLocation().equals(location)); |
| |
| breaks.deleteAllBreakpoints(); |
| |
| |
| /* clean up the session */ |
| session.terminate(); |
| session=null; |
| |
| } |
| |
| /*** |
| * A couple tests to make sure deleting breakpoints works as expected |
| */ |
| public void testDelBreak() throws CoreException, MIException, IOException, CDIException { |
| ICDIBreakpointManager breaks; |
| ICDILocation location, savedLocation; |
| ICDIBreakpoint[] breakpoints, savedbreakpoints; |
| ICDILocationBreakpoint curbreak; |
| |
| session=CDebugHelper.createSession("main", testProject); |
| assertNotNull(session); |
| breaks=session.getBreakpointManager(); |
| assertNotNull(breaks); |
| |
| /* Make sure initially we don't have any breakpoints */ |
| breakpoints=breaks.getBreakpoints(); |
| assertNotNull(breakpoints); |
| assertTrue(breakpoints.length==0); |
| |
| /********************************************************************** |
| * |
| * Test to make sure if we create a new breakpoint, we can delete |
| * it by passing a refrence to it to deleteBreakpoint() |
| * |
| **********************************************************************/ |
| |
| /* Create a break point on a generic function */ |
| location=breaks.createLocation("main.c", "func1", 0); |
| assertNotNull(location); |
| curbreak=breaks.setLocationBreakpoint(0, location, null, null); |
| breaks.deleteBreakpoint(curbreak); |
| pause(); |
| /** |
| * we should not have any breakpoints left. |
| */ |
| breakpoints=breaks.getBreakpoints(); |
| assertTrue(breakpoints.length==0); |
| |
| /********************************************************************** |
| * |
| * Test to make sure if we create multiple new breakpoint, we can delete |
| * one of them by passing a refrence to it to deleteBreakpoint() |
| * |
| **********************************************************************/ |
| |
| /* Create a break point on a generic function */ |
| location=breaks.createLocation("main.c", "func1", 0); |
| assertNotNull(location); |
| curbreak=breaks.setLocationBreakpoint(0, location, null, null); |
| savedLocation=curbreak.getLocation(); |
| |
| location=breaks.createLocation("main.c", "main", 0); |
| assertNotNull(location); |
| curbreak=breaks.setLocationBreakpoint(0, location, null, null); |
| breaks.deleteBreakpoint(curbreak); |
| pause(); |
| breakpoints=breaks.getBreakpoints(); |
| /*** |
| * Make sure there is only 1 breakpoint left, and it's the one we expect |
| */ |
| assertTrue(breakpoints.length==1); |
| curbreak=(ICDILocationBreakpoint) breakpoints[0]; |
| assertNotNull(curbreak); |
| assertTrue(curbreak.getLocation().equals(savedLocation)); |
| /*** |
| * Then delete the other breakpoint. |
| */ |
| breaks.deleteBreakpoint(curbreak); |
| pause(); |
| breakpoints=breaks.getBreakpoints(); |
| assertTrue(breakpoints.length==0); |
| |
| /********************************************************************** |
| * Make sure deleteBreakpoints works when given 1 breakpoint to delete |
| **********************************************************************/ |
| savedbreakpoints= new ICDIBreakpoint[1]; |
| for (int x=0;x<10;x++) { |
| location=breaks.createLocation("main.c", null, x+1); |
| savedbreakpoints[0]=breaks.setLocationBreakpoint(0, location, null, null); |
| assertNotNull(savedbreakpoints[0]); |
| } |
| breaks.deleteBreakpoints(savedbreakpoints); |
| pause(); |
| /* We should now have 9 breakpoints left. */ |
| breakpoints=breaks.getBreakpoints(); |
| assertTrue(breakpoints.length==9); |
| /* Make sure we have the correct 9 breakpoints left */ |
| for (int x=0;x<breakpoints.length;x++) { |
| curbreak=(ICDILocationBreakpoint)breakpoints[x]; |
| assertTrue(curbreak.getLocation().getLineNumber()==x+1); |
| } |
| breaks.deleteAllBreakpoints(); |
| pause(); |
| assertTrue(breaks.getBreakpoints().length==0); |
| |
| /********************************************************************** |
| * Make sure deleteBreakpoints works when given more then 1 but less |
| * then all breakpoints to delete |
| **********************************************************************/ |
| savedbreakpoints= new ICDIBreakpoint[4]; |
| for (int x=0;x<10;x++) { |
| location=breaks.createLocation("main.c", null, x+1); |
| savedbreakpoints[x%4]=breaks.setLocationBreakpoint(0, location, null, null); |
| assertNotNull(savedbreakpoints[x%4]); |
| } |
| breaks.deleteBreakpoints(savedbreakpoints); |
| pause(); |
| |
| /* We should now have 6 breakpoints left. */ |
| breakpoints=breaks.getBreakpoints(); |
| assertTrue(breakpoints.length==6); |
| /* Make sure we have the correct 6 breakpoints left */ |
| for (int x=0;x<breakpoints.length;x++) { |
| curbreak=(ICDILocationBreakpoint)breakpoints[x]; |
| assertTrue(curbreak.getLocation().getLineNumber()==x+1); |
| } |
| breaks.deleteAllBreakpoints(); |
| pause(); |
| assertTrue(breaks.getBreakpoints().length==0); |
| |
| /********************************************************************** |
| * Make sure deleteBreakpoints works when given all the breakpoints |
| **********************************************************************/ |
| savedbreakpoints= new ICDIBreakpoint[10]; |
| for (int x=0;x<10;x++) { |
| location=breaks.createLocation("main.c", null, x+1); |
| savedbreakpoints[x]=breaks.setLocationBreakpoint(0, location, null, null); |
| assertNotNull(savedbreakpoints[x]); |
| } |
| breaks.deleteBreakpoints(savedbreakpoints); |
| pause(); |
| /* We should now have 0 breakpoints left. */ |
| breakpoints=breaks.getBreakpoints(); |
| assertTrue(breakpoints.length==0); |
| |
| /********************************************************************** |
| * Make sure deleteAllBreakpoints works |
| **********************************************************************/ |
| |
| for (int x=0;x<10;x++) { |
| location=breaks.createLocation("main.c", null, x+1); |
| curbreak=breaks.setLocationBreakpoint(0, location, null, null); |
| assertNotNull(curbreak); |
| } |
| breaks.deleteAllBreakpoints(); |
| pause(); |
| /* We should now have 0 breakpoints left. */ |
| breakpoints=breaks.getBreakpoints(); |
| assertTrue(breakpoints.length==0); |
| |
| |
| |
| /* clean up the session */ |
| session.terminate(); |
| session=null; |
| |
| } |
| /*** |
| * A couple tests to make sure setting breakpoints with conditions seems to |
| * work as expected. |
| */ |
| public void testCondBreak() throws CoreException, MIException, IOException, CDIException, InterruptedException { |
| ICDIBreakpointManager breaks; |
| ICDILocation location; |
| ICDICondition cond; |
| boolean caught=false; |
| session=CDebugHelper.createSession("main", testProject); |
| assertNotNull(session); |
| breaks=session.getBreakpointManager(); |
| assertNotNull(breaks); |
| |
| /********************************************************************** |
| * Create a break point on a generic function with an empty condition |
| **********************************************************************/ |
| cond=breaks.createCondition(0, ""); |
| location=breaks.createLocation(null, "func1", 0); |
| assertNotNull(location); |
| breaks.setLocationBreakpoint(0, location, cond, null); |
| |
| /********************************************************************** |
| * Create a break point on a generic function with an valid condition |
| **********************************************************************/ |
| cond=breaks.createCondition(0, "x<10"); |
| location=breaks.createLocation(null, "func1", 0); |
| assertNotNull(location); |
| breaks.setLocationBreakpoint(0, location, cond, null); |
| |
| /********************************************************************** |
| * Create a break point on a generic function with an invalid condition |
| * We expect to get a CDIException when we try to set the breakpoint. |
| **********************************************************************/ |
| cond=breaks.createCondition(0, "nonexist<10"); |
| location=breaks.createLocation(null, "func1", 0); |
| assertNotNull(location); |
| try { |
| breaks.setLocationBreakpoint(0, location, cond, null); |
| } catch (CDIException e) { |
| caught=true; |
| } |
| assertTrue(caught); |
| |
| /********************************************************************** |
| * Create a break point on a line number with a condition and make sure |
| * it does not suspend execution of the application until the condition |
| * is true |
| **********************************************************************/ |
| breaks.deleteAllBreakpoints(); |
| location=breaks.createLocation(null, null, 23); |
| assertNotNull(location); |
| cond=breaks.createCondition(0, "a>10"); |
| |
| breaks.setLocationBreakpoint(0, location, cond, null); |
| targets=session.getTargets(); |
| /* We better only have one target connected to this session or something |
| * is not right... |
| */ |
| assertTrue(targets.length==1); |
| /* Resume the target, this should cause it to run till it hits the |
| * breakpoint |
| */ |
| targets[0].resume(); |
| /** |
| * Give the process up to 10 seconds to become either terminated or |
| * suspended. It sould hit the breakponint almost immediatly so we |
| * should only sleep for max 100 ms |
| */ |
| for (int x=0;x<100;x++) { |
| if (targets[0].isSuspended() || targets[0].isTerminated()) |
| break; |
| Thread.sleep(100); |
| } |
| assertTrue("Suspended: " + targets[0].isSuspended() + " Termiunated: " + targets[0].isTerminated(), targets[0].isSuspended()); |
| location=targets[0].getCurrentThread().getStackFrames()[0].getLocation(); |
| assertTrue(location.getLineNumber()==23); |
| assertTrue(location.getFunction().equals("main")); |
| assertTrue(location.getFile().equals("main.c")); |
| /* Get the value of a and and make sure it is 11 */ |
| assertTrue(targets[0].evaluateExpressionToString("a"), |
| targets[0].evaluateExpressionToString("a").equals("11")); |
| |
| |
| /* clean up the session */ |
| session.terminate(); |
| session=null; |
| targets=null; |
| |
| } |
| void pause() { |
| try { |
| Thread.sleep(100); |
| } catch (InterruptedException e) { |
| } |
| } |
| |
| } |