/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
package complex.memCheck;
import com.sun.star.beans.PropertyValue;
import com.sun.star.frame.XStorable;
import com.sun.star.lang.XComponent;
import com.sun.star.lang.XMultiServiceFactory;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.util.XCloseable;
// import complexlib.ComplexTestCase;
import helper.ProcessHandler;
import java.io.File;
// import java.io.FilePermission;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.PrintWriter;
import java.util.Enumeration;
import java.util.StringTokenizer;
import java.util.Vector;
import lib.*;
import util.DesktopTools;
// import util.WriterTools;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.openoffice.test.OfficeConnection;
import static org.junit.Assert.*;
/**
* Documents are opened and exported with StarOffice. The memory usage of
* StarOffice is monitored and if the usage exceeds the allowed kilobytes,
* the test is failed. Used for monitoring the StarOffice process is the
* command line tool 'pmap', available on Solaris or Linux. This test will not
* run on Windows.
Test procedure: every given document type is searched in
* the source directory
* Needed parameters:
*
* - "TestDocumentPath" - the path where test documents are located.
* - "AllowMemoryIncrease" (optional) - the allowed memory increase measured in kByte per exported document. The default is 10 kByte.
* - "ExportDocCount" (optional) - the amount of exports for each document that is loaded. Is defaulted to 25.
*
- "FileExportFilter" (optional) - a relation between loaded document type and used export filter. Is defaulted to
* writer, calc and impress. This parameter can be set with a number to give more than one relation. Example:
* "FileExportFilter1=sxw,writer_pdf_Export"
* "FileExportFilter2=sxc,calc_pdf_Export"
* "FileExportFilter3=sxi,impress_pdf_Export"
* All parameters are used for iteration over the test document path.
*
*/
class TempDir
{
private String m_sTempDir;
public TempDir(String _sTempDir)
{
m_sTempDir = _sTempDir;
}
public String getOfficeTempDir()
{
return m_sTempDir;
}
public String getTempDir()
{
final String sTempDir = FileHelper.getJavaCompatibleFilename(m_sTempDir);
return sTempDir;
}
}
public class CheckMemoryUsage /* extends ComplexTestCase */
{
private final String sWriterDoc = "sxw,writer_pdf_Export";
private final String sCalcDoc = "sxc,calc_pdf_Export";
private final String sImpressDoc = "sxi,impress_pdf_Export";
// private String sProcessIdCommand = null;
TempDir m_aTempDir;
// private String sFS = null;
// private String sMemoryMap1 = null;
// private String sMemoryMap2 = null;
// private String sDocumentPath = "";
private String[][] sDocTypeExportFilter;
private String[][] sDocuments;
private int iAllowMemoryIncrease = 10;
private int iExportDocCount = 25;
/**
* The test parameters
*/
private static TestParameters param = null;
/**
* Get all test methods
* @return The test methods.
// */
// public String[] getTestMethodNames() {
// return new String[] {"loadAndSaveDocuments"};
// }
/**
* Collect all documnets to load and all filters used for export.
*/
@Before
public void before()
{
final XMultiServiceFactory xMsf = getMSF();
// some Tests need the qadevOOo TestParameters, it is like a Hashmap for Properties.
param = new TestParameters();
param.put("ServiceFactory", xMsf); // some qadevOOo functions need the ServiceFactory
// test does definitely not run on Windows.
if (param.get("OperatingSystem").equals("wntmsci"))
{
System.out.println("Test can only reasonably be executed with a tool that "
+ "displays the memory usage of StarOffice.");
System.out.println("Test does not run on Windows, only on Solaris or Linux.");
// in an automatic environment it is better to say, there is no error here.
// it is a limitation, but no error.
System.exit(0);
}
// how many times is every document exported.
int count = param.getInt("ExportDocCount");
if (count != 0)
{
iExportDocCount = count;
}
// get the temp dir for creating the command scripts.
// sTempDir = System.getProperty("java.io.tmpdir");
m_aTempDir = new TempDir(util.utils.getOfficeTemp/*Dir*/(xMsf));
// get the file extension, export filter connection
Enumeration keys = param.keys();
Vector v = new Vector();
while (keys.hasMoreElements())
{
String key = (String) keys.nextElement();
if (key.startsWith("FileExportFilter"))
{
v.add((String) param.get(key));
}
}
// if no param given, set defaults.
if (v.size() == 0)
{
v.add(sWriterDoc);
v.add(sCalcDoc);
v.add(sImpressDoc);
}
// store a file extension
sDocTypeExportFilter = new String[v.size()][2];
for (int i = 0; i < v.size(); i++)
{
// 2do: error routine for wrong given params
final String sVContent = v.get(i);
StringTokenizer t = new StringTokenizer(sVContent, ",");
final String sExt = t.nextToken();
final String sName = t.nextToken();
sDocTypeExportFilter[i][0] = sExt;
sDocTypeExportFilter[i][1] = sName;
}
// get files to load and export
// sDocumentPath = (String) param.get("TestDocumentPath");
String sDocumentPath = TestDocument.getUrl();
File f = new File(FileHelper.getJavaCompatibleFilename(sDocumentPath));
// sDocumentPath = f.getAbsolutePath();
// String sFS = System.getProperty("file.separator");
sDocuments = new String[sDocTypeExportFilter.length][];
for (int j = 0; j < sDocTypeExportFilter.length; j++)
{
FileFilter filter = new FileFilter(sDocTypeExportFilter[j][0]);
String[] doc = f.list(filter);
sDocuments[j] = new String[doc.length];
for (int i = 0; i < doc.length; i++)
{
// final String sDocument = FileHelper.appendPath(sDocumentPath, doc[i]);
// sDocuments[j][i] = utils.getFullURL(sDocuments[j][i]);
sDocuments[j][i] = TestDocument.getUrl(doc[i]);
}
}
}
/**
* delete all created files on disk
*/
@After
public void after()
{
// delete the constructed files.
// we don't need to delete anything, all is stored in $USER_TREE
// for (int i = 0; i < iExportDocCount; i++)
// {
// final String sDocumentName = "DocExport" + i + ".pdf";
// final String sFilename = FileHelper.appendPath(m_sTempDir, sDocumentName);
// File f = new File(FileHelper.getJavaCompatibleFilename(sFilename));
// f.delete();
// }
// File f = new File(sProcessIdCommand);
// f.delete();
// f = new File(sOfficeMemoryCommand);
// f.delete();
}
/**
* The test function: load documents and save them using the given filters
* for each given document type.
*/
@Test
public void loadAndSaveDocuments()
{
int nOk = 0;
int nRunThrough = 0;
// At first:
// we load the document, there will be some post work in office like late initialisations
// we store exact one time the document
// so the memory footprint should be right
// iterate over all document types
for (int k = 0; k < sDocTypeExportFilter.length; k++)
{
// iterate over all documents of this type
for (int i = 0; i < sDocuments[k].length; i++)
{
final String sDocument = sDocuments[k][i];
final String sExtension = sDocTypeExportFilter[k][1];
// OfficeMemchecker aChecker = new OfficeMemchecker();
// aChecker.setDocumentName(FileHelper.getBasename(sDocument));
// aChecker.setExtension(sExtension);
// aChecker.start();
loadAndSaveNTimesDocument(sDocument, 1, sExtension);
// nOk += checkMemory(aChecker);
// nRunThrough ++;
}
System.out.println();
System.out.println();
}
shortWait(10000);
// Now the real test, load document and store 25 times
// iterate over all document types
for (int k = 0; k < sDocTypeExportFilter.length; k++)
{
// iterate over all documents of this type
for (int i = 0; i < sDocuments[k].length; i++)
{
final String sDocument = sDocuments[k][i];
final String sExtension = sDocTypeExportFilter[k][1];
OfficeMemchecker aChecker = new OfficeMemchecker();
aChecker.setDocumentName(FileHelper.getBasename(sDocument));
aChecker.setExtension(sExtension);
aChecker.start();
loadAndSaveNTimesDocument(sDocument, iExportDocCount, sExtension);
aChecker.stop();
final int nConsumMore = aChecker.getConsumMore();
nOk += checkMemory(nConsumMore);
nRunThrough++;
}
System.out.println();
System.out.println();
}
System.out.println("Find the output of used 'pmap' here: " + m_aTempDir.getTempDir() + " if test failed.");
assertTrue("Office consumes too many memory.", nOk == nRunThrough);
}
/**
* Checks how much memory should consum
* @param storageBefore
* @return 1 if consum is ok, else 0
*/
private int checkMemory(int nConsumMore)
{
int nAllowed = iAllowMemoryIncrease * iExportDocCount;
System.out.println("The Office consumes now " + nConsumMore
+ "K more memory than at the start of the test; allowed were "
+ nAllowed + "K.");
if (nConsumMore > nAllowed)
{
System.out.println("ERROR: This is not allowed.");
return 0;
}
System.out.println("OK.");
return 1;
}
/**
* load and save exact one document
*/
private void loadAndSaveNTimesDocument(String _sDocument, int _nCount, String _sStoreExtension)
{
System.out.println("Document: " + _sDocument);
XComponent xComponent = DesktopTools.loadDoc(getMSF(), _sDocument, null);
XStorable xStorable = UnoRuntime.queryInterface(XStorable.class, xComponent);
if (xStorable != null)
{
// export each document iExportDocCount times
for (int j = 0; j < _nCount; j++)
{
final String sDocumentName = FileHelper.getBasename(_sDocument) + "_" + j + ".pdf";
final String sFilename = FileHelper.appendPath(m_aTempDir.getOfficeTempDir(), sDocumentName);
// String url = utils.getFullURL(sFilename);
String url = sFilename; // graphical.FileHelper.getFileURLFromSystemPath(sFilename);
try
{
PropertyValue[] props = new PropertyValue[1];
props[0] = new PropertyValue();
props[0].Name = "FilterName";
// use export filter for this doc type
props[0].Value = _sStoreExtension;
xStorable.storeToURL(url, props);
}
catch (com.sun.star.io.IOException e)
{
fail("Could not store to '" + url + "'");
}
}
// close the doc
XCloseable xCloseable = UnoRuntime.queryInterface(XCloseable.class, xStorable);
try
{
xCloseable.close(true);
}
catch (com.sun.star.util.CloseVetoException e)
{
e.printStackTrace();
fail("Cannot close document: test is futile, Office will surely use more space.");
}
}
else
{
System.out.println("Cannot query for XStorable interface on document '" + _sDocument + "'");
System.out.println(" -> Skipping storage.");
}
}
// -----------------------------------------------------------------------------
private class OfficeMemchecker
{
/**
* After called start() it contains the memory need at startup
*/
private int m_nMemoryStart;
/**
* After called stop() it contains the memory usage
*/
private int m_nMemoryUsage;
private String m_sDocumentName;
private String m_sExtension;
public OfficeMemchecker()
{
m_nMemoryStart = 0;
}
public void setDocumentName(String _sDocName)
{
m_sDocumentName = _sDocName;
}
public void setExtension(String _sExt)
{
m_sExtension = _sExt;
}
public void start()
{
m_nMemoryStart = getOfficeMemoryUsage(createModeName("start", 0));
}
private String createModeName(String _sSub, int _nCount)
{
StringBuffer aBuf = new StringBuffer();
aBuf.append(_sSub);
aBuf.append('_').append(m_sDocumentName).append('_').append(m_sExtension);
aBuf.append('_').append(_nCount);
return aBuf.toString();
}
public void stop()
{
// short wait for the office to 'calm down' and free some memory
shortWait(20000);
// wait util memory is not freed anymore.
int storageAfter = getOfficeMemoryUsage(createModeName("stop", 0));
int mem = 0;
int count = 0;
while (storageAfter != mem && count < 10)
{
count++;
mem = storageAfter;
storageAfter = getOfficeMemoryUsage(createModeName("stop", count));
shortWait(1000);
}
m_nMemoryUsage = (storageAfter - m_nMemoryStart);
}
public int getConsumMore()
{
return m_nMemoryUsage;
}
/**
* Get the process ID from the Office
* @return the Id as String
*/
private String getOfficeProcessID()
{
String sProcessIdCommand = FileHelper.appendPath(m_aTempDir.getTempDir(), "getPS");
final String sofficeArg = org.openoffice.test.Argument.get("soffice");
final String sPSGrep = "ps -ef | grep $USER | grep .bin | grep -v grep";
final String sProcessId = sPSGrep.replaceAll("", FileHelper.getJavaCompatibleFilename(sofficeArg));
createExecutableFile(sProcessIdCommand, sProcessId);
ProcessHandler processID = new ProcessHandler(sProcessIdCommand);
processID.noOutput();
processID.executeSynchronously();
String text = processID.getOutputText();
if (text == null || text.equals("") || text.indexOf(' ') == -1)
{
fail("Could not determine Office process ID. Check " + sProcessIdCommand);
}
StringTokenizer aToken = new StringTokenizer(text);
// this is not nice, but ps gives the same output on every machine
aToken.nextToken();
String id = aToken.nextToken();
return id;
}
/**
* Get the memory usage of the Office in KByte.
* @return The memory used by the Office.
*/
private int getOfficeMemoryUsage(String _sMode)
{
final String sMemoryMonitor = "pmap |tee | grep total";
String sOfficeMemoryCommand = null;
sOfficeMemoryCommand = FileHelper.appendPath(m_aTempDir.getTempDir(), "getPmap");
// sOfficeMemoryCommand = FileHelper.getJavaCompatibleFilename(sOfficeMemoryCommand);
String command = sMemoryMonitor.replaceAll("", getOfficeProcessID());
String sPmapOutputFile = FileHelper.appendPath(m_aTempDir.getTempDir(), "pmap_" + _sMode + ".txt");
command = command.replaceAll("", sPmapOutputFile);
createExecutableFile(sOfficeMemoryCommand, command);
ProcessHandler processID = new ProcessHandler(sOfficeMemoryCommand);
processID.noOutput();
processID.executeSynchronously();
int nError = processID.getExitCode();
assertTrue("Execute of " + sOfficeMemoryCommand + " failed", nError == 0);
String text = processID.getOutputText();
if (text == null || text.equals("") || text.indexOf(' ') == -1)
{
fail("Could not determine Office memory usage. Check " + sOfficeMemoryCommand);
}
StringTokenizer aToken = new StringTokenizer(text);
// this works, because the output of pmap is quite standardized.
aToken.nextToken();
String mem = aToken.nextToken();
mem = mem.substring(0, mem.indexOf('K'));
Integer memory = new Integer(mem);
return memory.intValue();
}
/**
* Write a script file and set its rights to rwxrwxrwx.
* @param fileName The name of the created file
* @param line The commandline that has to be written inside of the file.
*/
private void createExecutableFile(String fileName, String line)
{
final String sChmod = "chmod a+x ";
final String bash = "#!/bin/bash";
try
{
String sFilename = FileHelper.getJavaCompatibleFilename(fileName);
PrintWriter fWriter = new PrintWriter(new FileWriter(sFilename));
fWriter.println(bash);
fWriter.println(line);
fWriter.close();
// change rights to rwxrwxrwx
ProcessHandler processID = new ProcessHandler(sChmod + sFilename);
processID.noOutput();
processID.executeSynchronously();
int nError = processID.getExitCode();
assertTrue("chmod failed. ", nError == 0);
}
catch (java.io.IOException e)
{
}
}
}
/**
* Let this thread sleep for some time
* @param milliSeconds time to wait in milliseconds.
*/
public static void shortWait(int milliSeconds)
{
System.out.println("Wait for: " + milliSeconds + "ms");
try
{
Thread.sleep(milliSeconds);
}
catch (java.lang.InterruptedException e)
{ // ignore
}
}
/**
* Own file filter, will just return ok for all files that end with a given
* suffix
*/
private class FileFilter implements FilenameFilter
{
private String suffix = null;
/**
* C'tor.
* @param suffix The suffix each filename should end with.
*/
public FileFilter(String suffix)
{
this.suffix = suffix;
}
/**
* Returns true, if the name of the file has the suffix given to the
* c'tor.
* @param name The filename that is tested.
* @param file Not used.
* @return True, if name ends with suffix.
*/
public boolean accept(File file, String name)
{
return name.endsWith(suffix);
}
}
private XMultiServiceFactory getMSF()
{
final XMultiServiceFactory xMSF1 = UnoRuntime.queryInterface(XMultiServiceFactory.class, connection.getComponentContext().getServiceManager());
return xMSF1;
}
// setup and close connections
@BeforeClass
public static void setUpConnection() throws Exception
{
System.out.println("setUpConnection()");
connection.setUp();
}
@AfterClass
public static void tearDownConnection()
throws InterruptedException, com.sun.star.uno.Exception
{
System.out.println("tearDownConnection()");
connection.tearDown();
}
private static final OfficeConnection connection = new OfficeConnection();
}