From d20b91cb41dd7ef95a8d91c8973742970f3889b7 Mon Sep 17 00:00:00 2001 From: Maria Guseva Date: Mon, 11 Jul 2016 19:14:14 +0300 Subject: [PATCH] SRADA-848: Restore unit tests sources missed after merge commit. Tests were unintentionally deleted with merge commit: fdef1eee000746e9eccbe71a80bbf7501407cad0 Change-Id: I8a32bfe857d0ca552b087c94fc3cb57737e862bd --- .../ui/file/FileChartBoardTest.java | 25 ++ .../ui/file/data/FileAccessDBTest.java | 61 +++++ .../ui/file/data/FileAccessorDBTest.java | 58 +++++ .../ui/file/data/FileApiDBTest.java | 113 +++++++++ .../ui/file/data/FileStatusDBTest.java | 59 +++++ .../ui/file/manager/FileDataMakerTest.java | 258 +++++++++++++++++++++ 6 files changed, 574 insertions(+) create mode 100644 org.tizen.dynamicanalyzer.test/src/org/tizen/dynamicanalyzer/ui/file/FileChartBoardTest.java create mode 100644 org.tizen.dynamicanalyzer.test/src/org/tizen/dynamicanalyzer/ui/file/data/FileAccessDBTest.java create mode 100644 org.tizen.dynamicanalyzer.test/src/org/tizen/dynamicanalyzer/ui/file/data/FileAccessorDBTest.java create mode 100644 org.tizen.dynamicanalyzer.test/src/org/tizen/dynamicanalyzer/ui/file/data/FileApiDBTest.java create mode 100644 org.tizen.dynamicanalyzer.test/src/org/tizen/dynamicanalyzer/ui/file/data/FileStatusDBTest.java create mode 100644 org.tizen.dynamicanalyzer.test/src/org/tizen/dynamicanalyzer/ui/file/manager/FileDataMakerTest.java diff --git a/org.tizen.dynamicanalyzer.test/src/org/tizen/dynamicanalyzer/ui/file/FileChartBoardTest.java b/org.tizen.dynamicanalyzer.test/src/org/tizen/dynamicanalyzer/ui/file/FileChartBoardTest.java new file mode 100644 index 0000000..6c278a5 --- /dev/null +++ b/org.tizen.dynamicanalyzer.test/src/org/tizen/dynamicanalyzer/ui/file/FileChartBoardTest.java @@ -0,0 +1,25 @@ +package org.tizen.dynamicanalyzer.ui.file; + +import static org.mockito.Mockito.mock; + +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.powermock.reflect.Whitebox; +import org.tizen.dynamicanalyzer.ui.file.manager.FileDataMaker; + +@Ignore("Not implemented") +@RunWith(MockitoJUnitRunner.class) +public class FileChartBoardTest { + + @Mock private FileDataMaker dataMake; + @InjectMocks private FileChartBoard chartBoard = mock(FileChartBoard.class); + + @Test + public void testAddNewFileChartRows() throws Exception { + Whitebox.invokeMethod(chartBoard,"addNewFileChartRows"); + } +} diff --git a/org.tizen.dynamicanalyzer.test/src/org/tizen/dynamicanalyzer/ui/file/data/FileAccessDBTest.java b/org.tizen.dynamicanalyzer.test/src/org/tizen/dynamicanalyzer/ui/file/data/FileAccessDBTest.java new file mode 100644 index 0000000..86bd879 --- /dev/null +++ b/org.tizen.dynamicanalyzer.test/src/org/tizen/dynamicanalyzer/ui/file/data/FileAccessDBTest.java @@ -0,0 +1,61 @@ +package org.tizen.dynamicanalyzer.ui.file.data; + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.io.FileUtils; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.tizen.dynamicanalyzer.common.AnalyzerConstants; +import org.tizen.dynamicanalyzer.database.DBTableRegistry; +import org.tizen.dynamicanalyzer.database.SqlConnectionManager; +import org.tizen.dynamicanalyzer.ui.file.model.FileAccess; + +public class FileAccessDBTest { + static FileAccessDB accessDB = null; + static FileAccess access = null; + static String path = null; + + @BeforeClass + public static void initDB() throws Exception { + Date date = new Date(); + SimpleDateFormat format = new SimpleDateFormat("_yyyy-MM-dd-HH-mm-ss"); + path = "app" + format.format(date); + + SqlConnectionManager.establishConnection(path + File.separator + + AnalyzerConstants.DATABASE_NAME); + + accessDB = new FileAccessDB(); + Set tableSet = new HashSet(); + tableSet.add(accessDB.getTableName()); + DBTableRegistry.createDBTables(tableSet); + + access = new FileAccess(1, 2, new Long(9412708),new Long(9412708), "read"); + } + + + @Test + public final void testSelect() { + accessDB.insert(access); + List list = accessDB.select(); + assertEquals(access.getAccessorId(), list.get(0).getAccessorId()); + assertEquals(access.getApiType(), list.get(0).getApiType()); + assertEquals(access.getStartTime(), list.get(0).getStartTime()); + assertEquals(access.getEndTime(), list.get(0).getEndTime()); + assertEquals(access.getTooltip(), list.get(0).getTooltip()); + } + + @AfterClass + public static void done() throws Exception { + SqlConnectionManager.closeConnection(); + FileUtils.deleteDirectory(new File(path)); + } + +} diff --git a/org.tizen.dynamicanalyzer.test/src/org/tizen/dynamicanalyzer/ui/file/data/FileAccessorDBTest.java b/org.tizen.dynamicanalyzer.test/src/org/tizen/dynamicanalyzer/ui/file/data/FileAccessorDBTest.java new file mode 100644 index 0000000..e0038b5 --- /dev/null +++ b/org.tizen.dynamicanalyzer.test/src/org/tizen/dynamicanalyzer/ui/file/data/FileAccessorDBTest.java @@ -0,0 +1,58 @@ +package org.tizen.dynamicanalyzer.ui.file.data; + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.io.FileUtils; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.tizen.dynamicanalyzer.common.AnalyzerConstants; +import org.tizen.dynamicanalyzer.database.DBTableRegistry; +import org.tizen.dynamicanalyzer.database.SqlConnectionManager; +import org.tizen.dynamicanalyzer.ui.file.model.FileAccessor; + +public class FileAccessorDBTest { + static FileAccessorDB accessorDB = null; + static FileAccessor accessor = null; + static String path = null; + + @BeforeClass + public static void initDB() throws Exception { + Date date = new Date(); + SimpleDateFormat format = new SimpleDateFormat("_yyyy-MM-dd-HH-mm-ss"); + path = "app" + format.format(date); + + SqlConnectionManager.establishConnection(path + File.separator + + AnalyzerConstants.DATABASE_NAME); + + accessorDB = new FileAccessorDB(); + Set tableSet = new HashSet(); + tableSet.add(accessorDB.getTableName()); + DBTableRegistry.createDBTables(tableSet); + + accessor = new FileAccessor(1, 2, "/temp/test1.txt", 10, 10, true); + } + + @Test + public final void testSelect() { + accessorDB.insert(accessor); + List list = accessorDB.select(); + assertEquals(accessor.getAccessorId(), list.get(0).getAccessorId()); + assertEquals(accessor.getFileId(), list.get(0).getFileId()); + assertEquals(accessor.getFilePath(), list.get(0).getFilePath()); + assertEquals(accessor.getPid(), list.get(0).getPid()); + } + + @AfterClass + public static void done() throws Exception { + SqlConnectionManager.closeConnection(); + FileUtils.deleteDirectory(new File(path)); + } +} diff --git a/org.tizen.dynamicanalyzer.test/src/org/tizen/dynamicanalyzer/ui/file/data/FileApiDBTest.java b/org.tizen.dynamicanalyzer.test/src/org/tizen/dynamicanalyzer/ui/file/data/FileApiDBTest.java new file mode 100644 index 0000000..c0e0fbe --- /dev/null +++ b/org.tizen.dynamicanalyzer.test/src/org/tizen/dynamicanalyzer/ui/file/data/FileApiDBTest.java @@ -0,0 +1,113 @@ +package org.tizen.dynamicanalyzer.ui.file.data; + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.io.FileUtils; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.tizen.dynamicanalyzer.common.AnalyzerConstants; +import org.tizen.dynamicanalyzer.database.DBTableRegistry; +import org.tizen.dynamicanalyzer.database.SqlConnectionManager; +import org.tizen.dynamicanalyzer.ui.file.model.FileAccessor; +import org.tizen.dynamicanalyzer.ui.file.model.FileEvent; +import org.tizen.dynamicanalyzer.util.Logger; + +public class FileApiDBTest { + static FileApiDB apiDB = null; + static FileEvent event = null; + static FileAccessorDB accessorDB = null; + static FileAccessor accessor = null; + static String path = null; + ArrayList> fileApiList = new ArrayList>(); + + @BeforeClass + public static void initDB() throws Exception { + Date date = new Date(); + SimpleDateFormat format = new SimpleDateFormat("_yyyy-MM-dd-HH-mm-ss"); + path = "app" + format.format(date); + + SqlConnectionManager.establishConnection(path + File.separator + + AnalyzerConstants.DATABASE_NAME); + + apiDB = new FileApiDB(); + accessorDB = new FileAccessorDB(); + Set tableSet = new HashSet(); + tableSet.add(apiDB.getTableName()); + tableSet.add(accessorDB.getTableName()); + DBTableRegistry.createDBTables(tableSet); + + // accessorId = 1, fileId = 2, pid = 10, tid = 10 + accessor = new FileAccessor(1, 2, "/temp/test1.txt", 10, 10, true); + event = new FileEvent(new Long(0), 1, 2, new Long(10), 1, 11, new Long(9412708), + new Long(27), new Long(6), null, null, 0 ); + + } + + @Test + public final void testSelectAPI() { + ArrayList> fileApiList = new ArrayList>(); + fileApiList.add(event.getDBData()); + apiDB.insert(fileApiList); + accessorDB.insert(accessor); + + // when the parent row is selected + ResultSet rs = apiDB.selectAPI(2, 1, true, null); // fileId, accessorId, isParent + if (rs != null) { + try { + while (rs.next()) { + assertEquals(event.getSeq(), rs.getLong(1)); + assertEquals(event.getFdValue(), rs.getLong(2)); + assertEquals(event.getFdApiType(), rs.getInt(3)); + assertEquals(event.getTime(), rs.getLong(4)); + assertEquals(event.getApiId(), rs.getInt(5)); + assertEquals(event.getErrno(), rs.getLong(6)); + assertEquals(event.getFileSize(), rs.getLong(7)); + assertEquals(event.getSize(), rs.getLong(8)); + assertEquals(event.getReturn(), rs.getString(9)); + assertEquals(event.getArgs(), rs.getString(10)); + assertEquals("/temp/test1.txt", rs.getString(11)); + assertEquals(10, rs.getInt(12)); + assertEquals(10, rs.getInt(13)); + } + } catch (SQLException e) { + Logger.exception(e); + } finally { + SqlConnectionManager.releaseResultSet(rs); + } + } + } + + @Test + public final void testSelectFileEventBySeq() { + ResultSet rs = apiDB.selectFileEventBySeq(0); + if (rs != null) { + try { + while (rs.next()) { + assertEquals(1, rs.getInt(1)); // accessorId + } + } catch (SQLException e) { + Logger.exception(e); + } finally { + SqlConnectionManager.releaseResultSet(rs); + } + } + } + + @AfterClass + public static void done() throws Exception { + SqlConnectionManager.closeConnection(); + FileUtils.deleteDirectory(new File(path)); + } + +} diff --git a/org.tizen.dynamicanalyzer.test/src/org/tizen/dynamicanalyzer/ui/file/data/FileStatusDBTest.java b/org.tizen.dynamicanalyzer.test/src/org/tizen/dynamicanalyzer/ui/file/data/FileStatusDBTest.java new file mode 100644 index 0000000..62130b3 --- /dev/null +++ b/org.tizen.dynamicanalyzer.test/src/org/tizen/dynamicanalyzer/ui/file/data/FileStatusDBTest.java @@ -0,0 +1,59 @@ +package org.tizen.dynamicanalyzer.ui.file.data; + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.io.FileUtils; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.tizen.dynamicanalyzer.common.AnalyzerConstants; +import org.tizen.dynamicanalyzer.database.DBTableRegistry; +import org.tizen.dynamicanalyzer.database.SqlConnectionManager; +import org.tizen.dynamicanalyzer.ui.file.model.FileStatus; + +public class FileStatusDBTest { + + static FileStatusDB statusDB = null; + static FileStatus status = null; + static String path = null; + + @BeforeClass + public static void initDB() throws Exception { + Date date = new Date(); + SimpleDateFormat format = new SimpleDateFormat("_yyyy-MM-dd-HH-mm-ss"); + path = "app" + format.format(date); + + SqlConnectionManager.establishConnection(path + File.separator + + AnalyzerConstants.DATABASE_NAME); + + statusDB = new FileStatusDB(); + Set tableSet = new HashSet(); + tableSet.add(statusDB.getTableName()); + DBTableRegistry.createDBTables(tableSet); + + status = new FileStatus(1, 0, new Long(9412708)); + } + + @Test + public final void testSelect() { + statusDB.insert(status); + List list = statusDB.select(); + assertEquals(status.getFileId(), list.get(0).getFileId()); + assertEquals(status.getApiType(), list.get(0).getApiType()); + assertEquals(status.getEventTime(), list.get(0).getEventTime()); + } + + @AfterClass + public static void done() throws Exception { + SqlConnectionManager.closeConnection(); + FileUtils.deleteDirectory(new File(path)); + } + +} diff --git a/org.tizen.dynamicanalyzer.test/src/org/tizen/dynamicanalyzer/ui/file/manager/FileDataMakerTest.java b/org.tizen.dynamicanalyzer.test/src/org/tizen/dynamicanalyzer/ui/file/manager/FileDataMakerTest.java new file mode 100644 index 0000000..4e76201 --- /dev/null +++ b/org.tizen.dynamicanalyzer.test/src/org/tizen/dynamicanalyzer/ui/file/manager/FileDataMakerTest.java @@ -0,0 +1,258 @@ +package org.tizen.dynamicanalyzer.ui.file.manager; + +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Random; + +import org.apache.commons.io.FileUtils; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.tizen.dynamicanalyzer.common.AnalyzerConstants; +import org.tizen.dynamicanalyzer.database.DBTableRegistry; +import org.tizen.dynamicanalyzer.database.SqlConnectionManager; +import org.tizen.dynamicanalyzer.ui.file.model.FileEvent; +import org.tizen.dynamicanalyzer.util.InternalLogger; +import org.tizen.dynamicanalyzer.util.Logger; + +@RunWith(value = Parameterized.class) +public class FileDataMakerTest { + static List fileEventList = new ArrayList(); + static List randomFileEventList = new ArrayList(); + static FileDataManager fileManager = FileDataManager.getInstance();; + static FileDataMaker dataMaker = new FileDataMaker(); + static String filePath = "/tmp/test1.txt"; + static String path; + + @BeforeClass + public static void setUp() throws Exception { + + Logger.init(InternalLogger.DEBUG); + + Date date = new Date(); + SimpleDateFormat format = new SimpleDateFormat("_yyyy-MM-dd-HH-mm-ss"); + path = "app" + format.format(date); + + SqlConnectionManager.closeConnection(); + SqlConnectionManager.establishConnection(path + File.separator + + AnalyzerConstants.DATABASE_NAME); + + DBTableRegistry.createDBTables(null); + + makeRandomObjects(); + } + + @AfterClass + public static void tearDown() throws Exception { + FileUtils.deleteDirectory(new File(path)); + } + + public FileDataMakerTest(FileEvent event1, FileEvent event2, FileEvent event3, FileEvent event4, + FileEvent event5, FileEvent event6, FileEvent event7, FileEvent event8, FileEvent event9, + FileEvent event10, FileEvent event11, FileEvent event12) { + fileEventList.clear(); + fileEventList.add(event1); + fileEventList.add(event2); + fileEventList.add(event3); + fileEventList.add(event4); + fileEventList.add(event5); + fileEventList.add(event6); + fileEventList.add(event7); + fileEventList.add(event8); + fileEventList.add(event9); + fileEventList.add(event10); + fileEventList.add(event11); + fileEventList.add(event12); + } + + @Test + public void testAddFileEvent() { + List list = new ArrayList(); + for(FileEvent event : fileEventList) { + if(event != null) { + dataMaker.addFileEvent(list, event, null); + } + } + assertTrue(list.size() > 0); + } + + @Test + public void testAddRandomFileEvent() { + List list = new ArrayList(); + for(FileEvent event : randomFileEventList) { + if(event != null) { + dataMaker.addFileEvent(list, event, null); + } + } + assertTrue(list.size() > 0); + } + + /*@Test + public void addFileAccessor() { + int pid = 1000; + int tid = 1000; + + try { + int[] res = Whitebox.invokeMethod(dataMaker, "addFileAccessor", filePath, pid, tid, true); + assertEquals(res[0], 1); + assertEquals(res[1], 2); + } catch (Exception e) { + Logger.exception(e); + } + }*/ + + public static void makeRandomObjects() { + randomFileEventList.clear(); + + FileEvent event = null; + for(int i=0; i<100; i++) { + Random random = new Random(); + String filePath = null; + int pid = 100; + int tid = random.nextInt(2) + 100; + int apiType = random.nextInt(6); + int apiId = 0; + switch(apiType){ + case 0: + apiId = 143; + StringBuffer buffer = new StringBuffer(); + buffer.append("tmp/test"); + buffer.append(random.nextInt(2) + 4); + buffer.append(".txt"); + filePath = buffer.toString(); + break; + case 1: + apiId = 210; + break; + case 2: + case 3: + apiId = 167; + break; + case 4: + case 5: + apiId = 169; + break; + } + long time = System.currentTimeMillis(); + if(apiType == 4){ // wirte start + event = new FileEvent(0, filePath, pid, tid, + 0, 14, 192, time, + new Long(20), new Long(0), null, null, 0, true); + randomFileEventList.add(event); + event = new FileEvent(0, filePath, pid, tid, + 0, 15, 192, time, + new Long(20), new Long(0), null, null, 0, true); + randomFileEventList.add(event); + }else if(apiType == 5) { // write end + event = new FileEvent(0, filePath, pid, tid, + 0, 16, 192, time, + new Long(20), new Long(0), null, null, 0, true); + randomFileEventList.add(event); + } + event = new FileEvent(0, filePath, pid, tid, + 0, apiType, apiId, time, + new Long(20), new Long(0), null, null, 0, true); + randomFileEventList.add(event); + } + } + + @Parameters + public static Collection getTestParameters() { + FileEvent open1 = new FileEvent(0, filePath, 1000, 2000, //seq, filePath, pid, tid + new Long(21), 0, 143, new Long(476904), // fd, apiType, apiId, eventTime, + new Long(20), new Long(0), null, null, 0, true); // fileSize, size, args, returnVal, errNo, target + + FileEvent lockStart1 = new FileEvent(0, filePath, 1000, 2000, + new Long(21), 14, 192, new Long(2477230), + new Long(20), new Long(0), null, null, 0, true); + + FileEvent lockEnd1 = new FileEvent(0, filePath, 1000, 2000, + new Long(21), 15, 192, new Long(2477253), + new Long(20), new Long(0), null, null, 0, true); + + FileEvent writeStart1 = new FileEvent(0, filePath, 1000, 2000, + new Long(21), 4, 169, new Long(4477461), + new Long(20), new Long(0), null, null, 0, true); + + FileEvent writeEnd1 = new FileEvent(0, filePath, 1000, 2000, + new Long(21), 5, 169, new Long(4477494), + new Long(20), new Long(22), null, null, 0, true); + + FileEvent unlock1 = new FileEvent(0, filePath, 1000, 2000, + new Long(21), 16, 192, new Long(6477710), + new Long(20), new Long(0), null, null, 0, true); + + FileEvent lockStart1_1 = new FileEvent(0, filePath, 1000, 2000, + new Long(21), 14, 192, new Long(6477720), + new Long(20), new Long(0), null, null, 0, true); + + FileEvent lockEnd1_1 = new FileEvent(0, filePath, 1000, 2000, + new Long(21), 15, 192, new Long(6477720), + new Long(20), new Long(0), null, null, 0, true); + + FileEvent readStart1 = new FileEvent(0, filePath, 1000, 2000, + new Long(21), 2, 167, new Long(9477930), + new Long(20), new Long(0), null, null, 0, true); + + FileEvent readEnd1 = new FileEvent(0, filePath, 1000, 2000, + new Long(21), 3, 167, new Long(9477973), + new Long(20), new Long(0), null, null, 0, true); + + FileEvent unlock1_1 = new FileEvent(0, filePath, 1000, 2000, + new Long(21), 16, 192, new Long(9477974), + new Long(20), new Long(0), null, null, 0, true); + + FileEvent close1 = new FileEvent(0, filePath, 1000, 2000, + new Long(21), 1, 146, new Long(9478019), + new Long(20), new Long(0), null, null, 0, true); + ///////////////////////////////////////////////////////////////// + FileEvent open2 = new FileEvent(0, filePath, 1000, 1000, + new Long(22), 0, 143, new Long(476910), + new Long(20), new Long(0), null, null, 0, true); + + FileEvent writeStart2 = new FileEvent(0, filePath, 1000, 1000, + new Long(22), 4, 169, new Long(4477461), + new Long(20), new Long(0), null, null, 0, true); + + FileEvent writeEnd2 = new FileEvent(0, filePath, 1000, 1000, + new Long(22), 5, 169, new Long(4477500), + new Long(20), new Long(0), null, null, 0, true); + + FileEvent readStart2 = new FileEvent(0, filePath, 1000, 1000, + new Long(22), 2, 167, new Long(9477930), + new Long(20), new Long(0), null, null, 0, true); + + FileEvent readEnd2 = new FileEvent(0, filePath, 1000, 1000, + new Long(22), 3, 167, new Long(9477980), + new Long(20), new Long(0), null, null, 0, true); + + FileEvent close2 = new FileEvent(0, filePath, 1000, 1000, + new Long(22), 0, 210, new Long(9478020), + new Long(20), new Long(0), null, null, 2, true); + + //case1.basic test + FileEvent event1[] = {open1, lockStart1, lockEnd1, writeStart1, writeEnd1, unlock1, lockStart1_1, lockEnd1_1, + readStart1, readEnd1, unlock1_1, close1}; + + //case2.concurrent read/write test + FileEvent event2[] = {open1, open2, writeStart1, writeStart2, writeEnd1, writeEnd2, + readStart1, readStart2, readEnd1, readEnd2, close1, close2}; + + //case3.not open test + FileEvent event3[] = {lockStart1, lockEnd1, writeStart1, writeStart2, writeEnd1, writeEnd2, unlock1, + readStart1, readStart2, readEnd1, readEnd2, close1}; + + FileEvent list[][] = {event1, event2, event3}; + return Arrays.asList(list); + } +} -- 2.7.4