X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=test%2Fjalview%2Fcommands%2FEditCommandTest.java;h=111adbad261828684a3e28a3ec1b0267675ea69e;hb=574211a28b085a4bd67fe7a161cbd9773ecb75cf;hp=fe67e5f11dd0dae061a8d5e5aa3d95572d596556;hpb=838e4f91d4a53dd315640dbc9ff6ef7a815ee576;p=jalview.git diff --git a/test/jalview/commands/EditCommandTest.java b/test/jalview/commands/EditCommandTest.java index fe67e5f..111adba 100644 --- a/test/jalview/commands/EditCommandTest.java +++ b/test/jalview/commands/EditCommandTest.java @@ -1,6 +1,6 @@ /* - * Jalview - A Sequence Alignment Editor and Viewer (Version 2.9.0b1) - * Copyright (C) 2015 The Jalview Authors + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors * * This file is part of Jalview. * @@ -22,16 +22,25 @@ package jalview.commands; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertSame; +import static org.testng.AssertJUnit.assertTrue; import jalview.commands.EditCommand.Action; import jalview.commands.EditCommand.Edit; import jalview.datamodel.Alignment; import jalview.datamodel.AlignmentI; import jalview.datamodel.Sequence; +import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; +import jalview.datamodel.features.SequenceFeatures; +import jalview.gui.JvOptionPane; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; import java.util.Map; +import org.testng.Assert; +import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -43,6 +52,15 @@ import org.testng.annotations.Test; */ public class EditCommandTest { + private static Comparator BY_DESCRIPTION = new Comparator() + { + + @Override + public int compare(SequenceFeature o1, SequenceFeature o2) + { + return o1.getDescription().compareTo(o2.getDescription()); + } + }; private EditCommand testee; @@ -50,6 +68,22 @@ public class EditCommandTest private Alignment al; + /* + * compute n(n+1)/2 e.g. + * func(5) = 5 + 4 + 3 + 2 + 1 = 15 + */ + private static int func(int i) + { + return i * (i + 1) / 2; + } + + @BeforeClass(alwaysRun = true) + public void setUpJvOptionPane() + { + JvOptionPane.setInteractiveMode(false); + JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION); + } + @BeforeMethod(alwaysRun = true) public void setUp() { @@ -239,7 +273,7 @@ public class EditCommandTest public void testReplace() { // seem to need a dataset sequence on the edited sequence here - seqs[1].setDatasetSequence(seqs[1]); + seqs[1].createDatasetSequence(); new EditCommand("", Action.REPLACE, "ZXY", new SequenceI[] { seqs[1] }, 4, 8, al); assertEquals("abcdefghjk", seqs[0].getSequenceAsString()); @@ -630,4 +664,315 @@ public class EditCommandTest assertEquals(ds2, unwound.get(ds2).getDatasetSequence()); assertEquals(ds3, unwound.get(ds3).getDatasetSequence()); } + + /** + * Test a cut action's relocation of sequence features + */ + @Test(groups = { "Functional" }) + public void testCut_withFeatures() + { + /* + * create sequence features before, after and overlapping + * a cut of columns/residues 4-7 + */ + SequenceI seq0 = seqs[0]; // abcdefghjk/1-10 + seq0.addSequenceFeature(new SequenceFeature("before", "", 1, 3, 0f, + null)); + seq0.addSequenceFeature(new SequenceFeature("overlap left", "", 2, 6, + 0f, null)); + seq0.addSequenceFeature(new SequenceFeature("internal", "", 5, 6, 0f, + null)); + seq0.addSequenceFeature(new SequenceFeature("overlap right", "", 7, 8, + 0f, null)); + seq0.addSequenceFeature(new SequenceFeature("after", "", 8, 10, 0f, + null)); + + /* + * add some contact features + */ + SequenceFeature internalContact = new SequenceFeature("disulphide bond", "", 5, + 6, 0f, null); + seq0.addSequenceFeature(internalContact); // should get deleted + SequenceFeature overlapLeftContact = new SequenceFeature( + "disulphide bond", "", 2, 6, 0f, null); + seq0.addSequenceFeature(overlapLeftContact); // should get deleted + SequenceFeature overlapRightContact = new SequenceFeature( + "disulphide bond", "", 5, 8, 0f, null); + seq0.addSequenceFeature(overlapRightContact); // should get deleted + SequenceFeature spanningContact = new SequenceFeature( + "disulphide bond", "", 2, 9, 0f, null); + seq0.addSequenceFeature(spanningContact); // should get shortened 3' + + /* + * cut columns 3-6 (base 0), residues d-g 4-7 + */ + Edit ec = testee.new Edit(Action.CUT, seqs, 3, 4, al); // cols 3-6 base 0 + EditCommand.cut(ec, new AlignmentI[] { al }); + + List sfs = seq0.getSequenceFeatures(); + SequenceFeatures.sortFeatures(sfs, true); + + assertEquals(5, sfs.size()); // features internal to cut were deleted + SequenceFeature sf = sfs.get(0); + assertEquals("before", sf.getType()); + assertEquals(1, sf.getBegin()); + assertEquals(3, sf.getEnd()); + sf = sfs.get(1); + assertEquals("disulphide bond", sf.getType()); + assertEquals(2, sf.getBegin()); + assertEquals(5, sf.getEnd()); // truncated by cut + sf = sfs.get(2); + assertEquals("overlap left", sf.getType()); + assertEquals(2, sf.getBegin()); + assertEquals(3, sf.getEnd()); // truncated by cut + sf = sfs.get(3); + assertEquals("after", sf.getType()); + assertEquals(4, sf.getBegin()); // shifted left by cut + assertEquals(6, sf.getEnd()); // shifted left by cut + sf = sfs.get(4); + assertEquals("overlap right", sf.getType()); + assertEquals(4, sf.getBegin()); // shifted left by cut + assertEquals(4, sf.getEnd()); // truncated by cut + } + + /** + * Test a cut action's relocation of sequence features, with full coverage of + * all possible feature and cut locations for a 5-position ungapped sequence + */ + @Test(groups = { "Functional" }) + public void testCut_withFeatures_exhaustive() + { + /* + * create a sequence features on each subrange of 1-5 + */ + SequenceI seq0 = new Sequence("seq", "ABCDE"); + AlignmentI alignment = new Alignment(new SequenceI[] { seq0 }); + alignment.setDataset(null); + for (int from = 1; from <= seq0.getLength(); from++) + { + for (int to = from; to <= seq0.getLength(); to++) + { + String desc = String.format("%d-%d", from, to); + SequenceFeature sf = new SequenceFeature("test", desc, from, to, + 0f, null); + sf.setValue("from", Integer.valueOf(from)); + sf.setValue("to", Integer.valueOf(to)); + seq0.addSequenceFeature(sf); + } + } + // sanity check + List sfs = seq0.getSequenceFeatures(); + assertEquals(func(5), sfs.size()); + + /* + * now perform all possible cuts of subranges of 1-5 (followed by Undo) + * and validate the resulting remaining sequence features! + */ + SequenceI[] sqs = new SequenceI[] { seq0 }; + + for (int from = 0; from < seq0.getLength(); from++) + { + for (int to = from; to < seq0.getLength(); to++) + { + EditCommand ec = new EditCommand("Cut", Action.CUT, sqs, from, (to + - from + 1), alignment); + final String msg = String.format("Cut %d-%d ", from + 1, to + 1); + + verifyCut(seq0, from, to, msg); + + /* + * undo and verify + */ + AlignmentI[] views = new AlignmentI[] { alignment }; + ec.undoCommand(views); + sfs = seq0.getSequenceFeatures(); + assertEquals("After undo of " + msg, func(5), sfs.size()); + verifyUndo(from, to, sfs); + + /* + * redo and verify + */ + ec.doCommand(views); + verifyCut(seq0, from, to, msg); + + /* + * undo ready for next cut + */ + ec.undoCommand(views); + } + } + } + + /** + * Verify by inspection that the sequence features left on the sequence after + * a cut match the expected results. The trick to this is that we can parse + * each feature's original start-end positions from its description. + * + * @param seq0 + * @param from + * @param to + * @param msg + */ + protected void verifyCut(SequenceI seq0, int from, int to, + final String msg) + { + List sfs; + sfs = seq0.getSequenceFeatures(); + + Collections.sort(sfs, BY_DESCRIPTION); + + /* + * confirm the number of features has reduced by the + * number of features within the cut region i.e. by + * func(length of cut); exception is a cut at start or end of sequence, + * which retains the original coordinates, dataset sequence + * and all its features + */ + boolean datasetRetained = from == 0 || to == 4; + if (datasetRetained) + { + // dataset and all features retained + assertEquals(msg, func(5), sfs.size()); + } + else if (to - from == 4) + { + // all columns were cut + assertTrue(sfs.isEmpty()); + } + else + { + // failure in checkFeatureRelocation is more informative! + assertEquals(msg + "wrong number of features left", func(5) + - func(to - from + 1), sfs.size()); + } + + /* + * inspect individual features + */ + for (SequenceFeature sf : sfs) + { + verifyFeatureRelocation(sf, from + 1, to + 1, !datasetRetained); + } + } + + /** + * Check that after Undo, every feature has start/end that match its original + * "start" and "end" properties + * + * @param from + * @param to + * @param sfs + */ + protected void verifyUndo(int from, int to, List sfs) + { + for (SequenceFeature sf : sfs) + { + final int oldFrom = ((Integer) sf.getValue("from")).intValue(); + final int oldTo = ((Integer) sf.getValue("to")).intValue(); + String msg = String.format( + "Undo cut of [%d-%d], feature at [%d-%d] ", from + 1, to + 1, + oldFrom, oldTo); + assertEquals(msg + "start", oldFrom, sf.getBegin()); + assertEquals(msg + "end", oldTo, sf.getEnd()); + } + } + + /** + * Helper method to check a feature has been correctly relocated after a cut + * + * @param sf + * @param from + * start of cut (first residue cut) + * @param to + * end of cut (last residue cut) + * @param newDataset + */ + private void verifyFeatureRelocation(SequenceFeature sf, int from, int to, + boolean newDataset) + { + // TODO handle the gapped sequence case as well + int cutSize = to - from + 1; + final int oldFrom = ((Integer) sf.getValue("from")).intValue(); + final int oldTo = ((Integer) sf.getValue("to")).intValue(); + + String msg = String.format( + "Feature %s relocated to %d-%d after cut of %d-%d", + sf.getDescription(), sf.getBegin(), sf.getEnd(), from, to); + if (!newDataset) + { + // dataset retained with all features unchanged + assertEquals("0: " + msg, oldFrom, sf.getBegin()); + assertEquals("0: " + msg, oldTo, sf.getEnd()); + } + else if (oldTo < from) + { + // before cut region so unchanged + assertEquals("1: " + msg, oldFrom, sf.getBegin()); + assertEquals("2: " + msg, oldTo, sf.getEnd()); + } + else if (oldFrom > to) + { + // follows cut region - shift by size of cut + assertEquals("3: " + msg, newDataset ? oldFrom - cutSize : oldFrom, + sf.getBegin()); + assertEquals("4: " + msg, newDataset ? oldTo - cutSize : oldTo, + sf.getEnd()); + } + else if (oldFrom < from && oldTo > to) + { + // feature encloses cut region - shrink it right + assertEquals("5: " + msg, oldFrom, sf.getBegin()); + assertEquals("6: " + msg, oldTo - cutSize, sf.getEnd()); + } + else if (oldFrom < from) + { + // feature overlaps left side of cut region - truncated right + assertEquals("7: " + msg, from - 1, sf.getEnd()); + } + else if (oldTo > to) + { + // feature overlaps right side of cut region - truncated left + assertEquals("8: " + msg, newDataset ? from : to + 1, sf.getBegin()); + assertEquals("9: " + msg, newDataset ? from + oldTo - to - 1 : oldTo, + sf.getEnd()); + } + else + { + // feature internal to cut - should have been deleted! + Assert.fail(msg + " - should have been deleted"); + } + } + + /** + * Test a cut action's relocation of sequence features + */ + @Test(groups = { "Functional" }) + public void testCut_withFeatures5prime() + { + SequenceI seq0 = new Sequence("seq/8-11", "A-BCC"); + seq0.createDatasetSequence(); + assertEquals(8, seq0.getStart()); + seq0.addSequenceFeature(new SequenceFeature("", "", 10, 11, 0f, + null)); + SequenceI[] seqsArray = new SequenceI[] { seq0 }; + AlignmentI alignment = new Alignment(seqsArray); + + /* + * cut columns of A-B; same dataset sequence is retained, aligned sequence + * start becomes 10 + */ + Edit ec = testee.new Edit(Action.CUT, seqsArray, 0, 3, alignment); + EditCommand.cut(ec, new AlignmentI[] { alignment }); + + /* + * feature on CC(10-11) should still be on CC(10-11) + */ + assertSame(seq0, alignment.getSequenceAt(0)); + assertEquals(10, seq0.getStart()); + List sfs = seq0.getSequenceFeatures(); + assertEquals(1, sfs.size()); + SequenceFeature sf = sfs.get(0); + assertEquals(10, sf.getBegin()); + assertEquals(11, sf.getEnd()); + } }