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