1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 package complex.memCheck;
24 
25 import com.sun.star.beans.PropertyValue;
26 import com.sun.star.frame.XStorable;
27 import com.sun.star.lang.XComponent;
28 import com.sun.star.lang.XMultiServiceFactory;
29 import com.sun.star.uno.UnoRuntime;
30 import com.sun.star.util.XCloseable;
31 // import complexlib.ComplexTestCase;
32 import helper.ProcessHandler;
33 import java.io.File;
34 // import java.io.FilePermission;
35 import java.io.FileWriter;
36 import java.io.FilenameFilter;
37 import java.io.PrintWriter;
38 import java.util.Enumeration;
39 import java.util.StringTokenizer;
40 import java.util.Vector;
41 import lib.*;
42 import util.DesktopTools;
43 // import util.WriterTools;
44 
45 import org.junit.After;
46 import org.junit.AfterClass;
47 import org.junit.Before;
48 import org.junit.BeforeClass;
49 import org.junit.Test;
50 import org.openoffice.test.OfficeConnection;
51 import static org.junit.Assert.*;
52 
53 /**
54  * Documents are opened and exported with StarOffice. The memory usage of
55  * StarOffice is monitored and if the usage exceeds the allowed kilobytes,
56  * the test is failed. Used for monitoring the StarOffice process is the
57  * command line tool 'pmap', available on Solaris or Linux. This test will not
58  * run on Windows.<br>Test procedure: every given document type is searched in
59  * the source directory
60  * Needed paramters:
61  * <ul>
62  *   <li>"TestDocumentPath" - the path where test documents are located.</li>
63  *   <li>"AllowMemoryIncrease" (optional) - the allowed memory increase measured in kByte per exported document. The default is 10 kByte.</li>
64  *   <li>"ExportDocCount" (optional) - the amount of exports for each document that is loaded. Is defaulted to 25.
65  *   <li>"FileExportFilter" (optional) - a relation between loaded document type and used export filter. Is defaulted to
66  *       writer, calc and impress. This parameter can be set with a number to give more than one relation. Example:<br>
67  *       "FileExportFilter1=sxw,writer_pdf_Export"<br>
68  *       "FileExportFilter2=sxc,calc_pdf_Export"<br>
69  *       "FileExportFilter3=sxi,impress_pdf_Export"<br></li>
70  *       All parameters are used for iteration over the test document path.
71  * </ul>
72  */
73 class TempDir
74 {
75 
76     private String m_sTempDir;
77 
TempDir(String _sTempDir)78     public TempDir(String _sTempDir)
79     {
80         m_sTempDir = _sTempDir;
81     }
82 
getOfficeTempDir()83     public String getOfficeTempDir()
84     {
85         return m_sTempDir;
86     }
87 
getTempDir()88     public String getTempDir()
89     {
90         final String sTempDir = FileHelper.getJavaCompatibleFilename(m_sTempDir);
91         return sTempDir;
92     }
93 }
94 
95 public class CheckMemoryUsage /* extends ComplexTestCase */
96 
97 {
98 
99     private final String sWriterDoc = "sxw,writer_pdf_Export";
100     private final String sCalcDoc = "sxc,calc_pdf_Export";
101     private final String sImpressDoc = "sxi,impress_pdf_Export";
102     // private String sProcessIdCommand = null;
103     TempDir m_aTempDir;
104     // private String sFS = null;
105     // private String sMemoryMap1 = null;
106     // private String sMemoryMap2 = null;
107     // private String sDocumentPath = "";
108     private String[][] sDocTypeExportFilter;
109     private String[][] sDocuments;
110     private int iAllowMemoryIncrease = 10;
111     private int iExportDocCount = 25;
112     /**
113      * The test parameters
114      */
115     private static TestParameters param = null;
116 
117     /**
118      * Get all test methods
119      * @return The test methods.
120     //     */
121 //    public String[] getTestMethodNames() {
122 //        return new String[] {"loadAndSaveDocuments"};
123 //    }
124     /**
125      * Collect all documnets to load and all filters used for export.
126      */
127     @Before
before()128     public void before()
129     {
130 
131         final XMultiServiceFactory xMsf = getMSF();
132 
133         // some Tests need the qadevOOo TestParameters, it is like a Hashmap for Properties.
134         param = new TestParameters();
135         param.put("ServiceFactory", xMsf); // some qadevOOo functions need the ServiceFactory
136 
137         // test does definitely not run on Windows.
138         if (param.get("OperatingSystem").equals("wntmsci"))
139         {
140             System.out.println("Test can only reasonably be executed with a tool that "
141                     + "displays the memory usage of StarOffice.");
142             System.out.println("Test does not run on Windows, only on Solaris or Linux.");
143             // in an automatic environment it is better to say, there is no error here.
144             // it is a limitation, but no error.
145             System.exit(0);
146         }
147 
148 
149         // how many times is every document exported.
150         int count = param.getInt("ExportDocCount");
151         if (count != 0)
152         {
153             iExportDocCount = count;
154         }
155 
156         // get the temp dir for creating the command scripts.
157         // sTempDir = System.getProperty("java.io.tmpdir");
158         m_aTempDir = new TempDir(util.utils.getOfficeTemp/*Dir*/(xMsf));
159 
160         // get the file extension, export filter connection
161         Enumeration keys = param.keys();
162         Vector<String> v = new Vector<String>();
163         while (keys.hasMoreElements())
164         {
165             String key = (String) keys.nextElement();
166             if (key.startsWith("FileExportFilter"))
167             {
168                 v.add((String) param.get(key));
169             }
170         }
171         // if no param given, set defaults.
172         if (v.size() == 0)
173         {
174             v.add(sWriterDoc);
175             v.add(sCalcDoc);
176             v.add(sImpressDoc);
177         }
178         // store a file extension
179         sDocTypeExportFilter = new String[v.size()][2];
180         for (int i = 0; i < v.size(); i++)
181         {
182             // 2do: error routine for wrong given params
183             final String sVContent = v.get(i);
184             StringTokenizer t = new StringTokenizer(sVContent, ",");
185             final String sExt = t.nextToken();
186             final String sName = t.nextToken();
187             sDocTypeExportFilter[i][0] = sExt;
188             sDocTypeExportFilter[i][1] = sName;
189         }
190 
191         // get files to load and export
192 //        sDocumentPath = (String) param.get("TestDocumentPath");
193         String sDocumentPath = TestDocument.getUrl();
194         File f = new File(FileHelper.getJavaCompatibleFilename(sDocumentPath));
195         // sDocumentPath = f.getAbsolutePath();
196         // String sFS = System.getProperty("file.separator");
197         sDocuments = new String[sDocTypeExportFilter.length][];
198         for (int j = 0; j < sDocTypeExportFilter.length; j++)
199         {
200             FileFilter filter = new FileFilter(sDocTypeExportFilter[j][0]);
201             String[] doc = f.list(filter);
202             sDocuments[j] = new String[doc.length];
203             for (int i = 0; i < doc.length; i++)
204             {
205                 // final String sDocument = FileHelper.appendPath(sDocumentPath, doc[i]);
206                 // sDocuments[j][i] = utils.getFullURL(sDocuments[j][i]);
207                 sDocuments[j][i] = TestDocument.getUrl(doc[i]);
208             }
209         }
210     }
211 
212     /**
213      * delete all created files on disk
214      */
215     @After
after()216     public void after()
217     {
218         // delete the constructed files.
219 // we don't need to delete anything, all is stored in $USER_TREE
220 //        for (int i = 0; i < iExportDocCount; i++)
221 //        {
222 //            final String sDocumentName = "DocExport" + i + ".pdf";
223 //            final String sFilename = FileHelper.appendPath(m_sTempDir, sDocumentName);
224 //            File f = new File(FileHelper.getJavaCompatibleFilename(sFilename));
225 //            f.delete();
226 //        }
227         // File f = new File(sProcessIdCommand);
228         // f.delete();
229         // f = new File(sOfficeMemoryCommand);
230         // f.delete();
231     }
232 
233     /**
234      * The test function: load documents and save them using the given filters
235      * for each given document type.
236      */
237     @Test
loadAndSaveDocuments()238     public void loadAndSaveDocuments()
239     {
240         int nOk = 0;
241         int nRunThrough = 0;
242 
243         // At first:
244         // we load the document, there will be some post work in office like late initialisations
245         // we store exact one time the document
246         // so the memory footprint should be right
247 
248         // iterate over all document types
249         for (int k = 0; k < sDocTypeExportFilter.length; k++)
250         {
251             // iterate over all documents of this type
252             for (int i = 0; i < sDocuments[k].length; i++)
253             {
254 
255                 final String sDocument = sDocuments[k][i];
256                 final String sExtension = sDocTypeExportFilter[k][1];
257 
258 //                OfficeMemchecker aChecker = new OfficeMemchecker();
259 //                aChecker.setDocumentName(FileHelper.getBasename(sDocument));
260 //                aChecker.setExtension(sExtension);
261 //                aChecker.start();
262 
263                 loadAndSaveNTimesDocument(sDocument, 1, sExtension);
264 
265 //                nOk += checkMemory(aChecker);
266 //                nRunThrough ++;
267             }
268             System.out.println();
269             System.out.println();
270         }
271 
272         shortWait(10000);
273 
274         // Now the real test, load document and store 25 times
275 
276         // iterate over all document types
277         for (int k = 0; k < sDocTypeExportFilter.length; k++)
278         {
279             // iterate over all documents of this type
280             for (int i = 0; i < sDocuments[k].length; i++)
281             {
282 
283                 final String sDocument = sDocuments[k][i];
284                 final String sExtension = sDocTypeExportFilter[k][1];
285 
286                 OfficeMemchecker aChecker = new OfficeMemchecker();
287                 aChecker.setDocumentName(FileHelper.getBasename(sDocument));
288                 aChecker.setExtension(sExtension);
289                 aChecker.start();
290 
291                 loadAndSaveNTimesDocument(sDocument, iExportDocCount, sExtension);
292 
293                 aChecker.stop();
294                 final int nConsumMore = aChecker.getConsumMore();
295 
296                 nOk += checkMemory(nConsumMore);
297                 nRunThrough++;
298             }
299             System.out.println();
300             System.out.println();
301         }
302         System.out.println("Find the output of used 'pmap' here: " + m_aTempDir.getTempDir() + " if test failed.");
303         assertTrue("Office consumes too many memory.", nOk == nRunThrough);
304     }
305 
306     /**
307      * Checks how much memory should consum
308      * @param storageBefore
309      * @return 1 if consum is ok, else 0
310      */
checkMemory(int nConsumMore)311     private int checkMemory(int nConsumMore)
312     {
313         int nAllowed = iAllowMemoryIncrease * iExportDocCount;
314         System.out.println("The Office consumes now " + nConsumMore
315                 + "K more memory than at the start of the test; allowed were "
316                 + nAllowed + "K.");
317         if (nConsumMore > nAllowed)
318         {
319             System.out.println("ERROR: This is not allowed.");
320             return 0;
321         }
322         System.out.println("OK.");
323         return 1;
324     }
325 
326     /**
327      * load and save exact one document
328      */
loadAndSaveNTimesDocument(String _sDocument, int _nCount, String _sStoreExtension)329     private void loadAndSaveNTimesDocument(String _sDocument, int _nCount, String _sStoreExtension)
330     {
331         System.out.println("Document: " + _sDocument);
332         XComponent xComponent = DesktopTools.loadDoc(getMSF(), _sDocument, null);
333         XStorable xStorable = UnoRuntime.queryInterface(XStorable.class, xComponent);
334         if (xStorable != null)
335         {
336             // export each document iExportDocCount times
337             for (int j = 0; j < _nCount; j++)
338             {
339                 final String sDocumentName = FileHelper.getBasename(_sDocument) + "_" + j + ".pdf";
340                 final String sFilename = FileHelper.appendPath(m_aTempDir.getOfficeTempDir(), sDocumentName);
341                 // String url = utils.getFullURL(sFilename);
342                 String url = sFilename; // graphical.FileHelper.getFileURLFromSystemPath(sFilename);
343                 try
344                 {
345                     PropertyValue[] props = new PropertyValue[1];
346                     props[0] = new PropertyValue();
347                     props[0].Name = "FilterName";
348                     // use export filter for this doc type
349                     props[0].Value = _sStoreExtension;
350                     xStorable.storeToURL(url, props);
351                 }
352                 catch (com.sun.star.io.IOException e)
353                 {
354                     fail("Could not store to '" + url + "'");
355                 }
356             }
357             // close the doc
358             XCloseable xCloseable = UnoRuntime.queryInterface(XCloseable.class, xStorable);
359             try
360             {
361                 xCloseable.close(true);
362             }
363             catch (com.sun.star.util.CloseVetoException e)
364             {
365                 e.printStackTrace();
366                 fail("Cannot close document: test is futile, Office will surely use more space.");
367             }
368         }
369         else
370         {
371             System.out.println("Cannot query for XStorable interface on document '" + _sDocument + "'");
372             System.out.println(" -> Skipping storage.");
373         }
374 
375     }
376 
377 // -----------------------------------------------------------------------------
378     private class OfficeMemchecker
379     {
380 
381         /**
382          * After called start() it contains the memory need at startup
383          */
384         private int m_nMemoryStart;
385         /**
386          * After called stop() it contains the memory usage
387          */
388         private int m_nMemoryUsage;
389         private String m_sDocumentName;
390         private String m_sExtension;
391 
OfficeMemchecker()392         public OfficeMemchecker()
393         {
394             m_nMemoryStart = 0;
395         }
396 
setDocumentName(String _sDocName)397         public void setDocumentName(String _sDocName)
398         {
399             m_sDocumentName = _sDocName;
400         }
401 
setExtension(String _sExt)402         public void setExtension(String _sExt)
403         {
404             m_sExtension = _sExt;
405         }
406 
start()407         public void start()
408         {
409             m_nMemoryStart = getOfficeMemoryUsage(createModeName("start", 0));
410         }
411 
createModeName(String _sSub, int _nCount)412         private String createModeName(String _sSub, int _nCount)
413         {
414             StringBuffer aBuf = new StringBuffer();
415             aBuf.append(_sSub);
416             aBuf.append('_').append(m_sDocumentName).append('_').append(m_sExtension);
417             aBuf.append('_').append(_nCount);
418             return aBuf.toString();
419         }
420 
stop()421         public void stop()
422         {
423             // short wait for the office to 'calm down' and free some memory
424             shortWait(20000);
425             // wait util memory is not freed anymore.
426             int storageAfter = getOfficeMemoryUsage(createModeName("stop", 0));
427             int mem = 0;
428             int count = 0;
429             while (storageAfter != mem && count < 10)
430             {
431                 count++;
432                 mem = storageAfter;
433                 storageAfter = getOfficeMemoryUsage(createModeName("stop", count));
434                 shortWait(1000);
435             }
436             m_nMemoryUsage = (storageAfter - m_nMemoryStart);
437         }
438 
getConsumMore()439         public int getConsumMore()
440         {
441             return m_nMemoryUsage;
442         }
443 
444         /**
445          * Get the process ID from the Office
446          * @return the Id as String
447          */
getOfficeProcessID()448         private String getOfficeProcessID()
449         {
450             String sProcessIdCommand = FileHelper.appendPath(m_aTempDir.getTempDir(), "getPS");
451             final String sofficeArg = org.openoffice.test.Argument.get("soffice");
452             final String sPSGrep = "ps -ef | grep $USER | grep <soffice>.bin | grep -v grep";
453             final String sProcessId = sPSGrep.replaceAll("<soffice>", FileHelper.getJavaCompatibleFilename(sofficeArg));
454 
455             createExecutableFile(sProcessIdCommand, sProcessId);
456             ProcessHandler processID = new ProcessHandler(sProcessIdCommand);
457             processID.noOutput();
458             processID.executeSynchronously();
459             String text = processID.getOutputText();
460             if (text == null || text.equals("") || text.indexOf(' ') == -1)
461             {
462                 fail("Could not determine Office process ID. Check " + sProcessIdCommand);
463             }
464             StringTokenizer aToken = new StringTokenizer(text);
465             // this is not nice, but ps gives the same output on every machine
466             aToken.nextToken();
467             String id = aToken.nextToken();
468             return id;
469         }
470 
471         /**
472          * Get the memory usage of the Office in KByte.
473          * @return The memory used by the Office.
474          */
getOfficeMemoryUsage(String _sMode)475         private int getOfficeMemoryUsage(String _sMode)
476         {
477             final String sMemoryMonitor = "pmap <processID> |tee <pmapoutputfile> | grep total";
478             String sOfficeMemoryCommand = null;
479             sOfficeMemoryCommand = FileHelper.appendPath(m_aTempDir.getTempDir(), "getPmap");
480             // sOfficeMemoryCommand = FileHelper.getJavaCompatibleFilename(sOfficeMemoryCommand);
481             String command = sMemoryMonitor.replaceAll("<processID>", getOfficeProcessID());
482             String sPmapOutputFile = FileHelper.appendPath(m_aTempDir.getTempDir(), "pmap_" + _sMode + ".txt");
483             command = command.replaceAll("<pmapoutputfile>", sPmapOutputFile);
484             createExecutableFile(sOfficeMemoryCommand, command);
485 
486             ProcessHandler processID = new ProcessHandler(sOfficeMemoryCommand);
487             processID.noOutput();
488             processID.executeSynchronously();
489             int nError = processID.getExitCode();
490             assertTrue("Execute of " + sOfficeMemoryCommand + " failed", nError == 0);
491             String text = processID.getOutputText();
492             if (text == null || text.equals("") || text.indexOf(' ') == -1)
493             {
494                 fail("Could not determine Office memory usage. Check " + sOfficeMemoryCommand);
495             }
496             StringTokenizer aToken = new StringTokenizer(text);
497             // this works, because the output of pmap is quite standardized.
498             aToken.nextToken();
499             String mem = aToken.nextToken();
500             mem = mem.substring(0, mem.indexOf('K'));
501             Integer memory = new Integer(mem);
502             return memory.intValue();
503         }
504 
505         /**
506          * Write a script file and set its rights to rwxrwxrwx.
507          * @param fileName The name of the created file
508          * @param line The commandline that has to be written inside of the file.
509          */
createExecutableFile(String fileName, String line)510         private void createExecutableFile(String fileName, String line)
511         {
512             final String sChmod = "chmod a+x ";
513             final String bash = "#!/bin/bash";
514 
515             try
516             {
517                 String sFilename = FileHelper.getJavaCompatibleFilename(fileName);
518                 PrintWriter fWriter = new PrintWriter(new FileWriter(sFilename));
519                 fWriter.println(bash);
520                 fWriter.println(line);
521                 fWriter.close();
522                 // change rights to rwxrwxrwx
523                 ProcessHandler processID = new ProcessHandler(sChmod + sFilename);
524                 processID.noOutput();
525                 processID.executeSynchronously();
526                 int nError = processID.getExitCode();
527                 assertTrue("chmod failed. ", nError == 0);
528             }
529             catch (java.io.IOException e)
530             {
531             }
532         }
533     }
534 
535     /**
536      * Let this thread sleep for some time
537      * @param milliSeconds time to wait in milliseconds.
538      */
shortWait(int milliSeconds)539     public static void shortWait(int milliSeconds)
540     {
541         System.out.println("Wait for: " + milliSeconds + "ms");
542         try
543         {
544             Thread.sleep(milliSeconds);
545         }
546         catch (java.lang.InterruptedException e)
547         { // ignore
548         }
549     }
550 
551     /**
552      * Own file filter, will just return ok for all files that end with a given
553      * suffix
554      */
555     private class FileFilter implements FilenameFilter
556     {
557 
558         private String suffix = null;
559 
560         /**
561          * C'tor.
562          * @param suffix The suffix each filename should end with.
563          */
FileFilter(String suffix)564         public FileFilter(String suffix)
565         {
566             this.suffix = suffix;
567         }
568 
569         /**
570          * Returns true, if the name of the file has the suffix given to the
571          * c'tor.
572          * @param name The filename that is tested.
573          * @param file Not used.
574          * @return True, if name ends with suffix.
575          */
accept(File file, String name)576         public boolean accept(File file, String name)
577         {
578             return name.endsWith(suffix);
579         }
580     }
581 
getMSF()582     private XMultiServiceFactory getMSF()
583     {
584         final XMultiServiceFactory xMSF1 = UnoRuntime.queryInterface(XMultiServiceFactory.class, connection.getComponentContext().getServiceManager());
585         return xMSF1;
586     }
587 
588     // setup and close connections
589     @BeforeClass
setUpConnection()590     public static void setUpConnection() throws Exception
591     {
592         System.out.println("setUpConnection()");
593         connection.setUp();
594     }
595 
596     @AfterClass
tearDownConnection()597     public static void tearDownConnection()
598             throws InterruptedException, com.sun.star.uno.Exception
599     {
600         System.out.println("tearDownConnection()");
601         connection.tearDown();
602     }
603     private static final OfficeConnection connection = new OfficeConnection();
604 }
605