1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
package Torello.Java.Build;

import static Torello.Java.C.*;

import Torello.Java.StorageWriter;
import Torello.Java.EXCC;
import Torello.Java.FileNode;
import Torello.Java.FileRW;
import Torello.Java.RTC;
import Torello.Java.C;

import Torello.Java.Additional.ConstantPool;
import Torello.Java.Additional.ByteArrClassLoader;
import Torello.Java.Additional.Ret3;

import Torello.Java.ReadOnly.ReadOnlyList;
import Torello.Java.ReadOnly.ReadOnlyArrayList;
import Torello.Java.ReadOnly.ROArrayListBuilder;

import java.io.IOException;
import java.io.File;

// This class has a single method (with one, small, static helper method) that just iterates 
// through a list of BuildPackage instances.  It looks for the root package directory, and then
// checks if the My/Package/Directory/ has a sub-directory names 'test-files/'.  Whenever it
// encounters a 'My/Package/Directory/test-files/', it scans that diretory (and all of its 
// sub-directories) to identify any Java '.class'-Files.
// 
// Each Java '.class'-File is the checked to see whether it implements the interface
// 'Torello.Java.Build.TestingClass'.  If it does, then its 'runTests(StorageWriter)' method is
// invoked, and the results are saved into the Log-File Directory.
// 
// This class *REALLY* isn't Rocket-Science.  It sometimes looks (to me) kind of difficult because
// of things like "ByteArrclassLoader", and of course the profuse use of the 'FileNode' and
// 'Stream' classes.  I wrote the 'ByteArrClassLoader' to circumvent having the place all '.class'
// files inside of a Directory-Tree Structure that is required to mimic the Package-Name itself.
// Though writing the API for 'Torello.Java.Additional.ConstantPool' was quite a bit of work, it
// can be a real life-saver in sitatuions exactly like the need to write a 'ByteArrClassLoader'
// 
// If you just accept that loading Class-Files from a directory where the "Package-Name" and the
// Directory-Tree are completely and totally un-correlated and disjoing is *THE ONLY REASON* for
// needed the ByteArrClassLoader / getDeclaredConstructor / newInstance stuff, then maybe this
// class will look as simple as it really is!
// 
// It scans for all '.class' files which are in a 'package/test-files/' directory - INCLUDING ANY
// AND ALL SUB-DIRECTORIES - and then loads them into memory (using the 'ConstantPool' and the
// specialized Class-Loader).  Then, it runs the 'runTests' on any of those classes which the
// user has written and implement the 'TestingClass' interface!
// 
// That's the whole thing in a nutshell.
// 
// Oh, and one more thing (i forgot), the "ConstantPool" class isn't actually used inside this 
// class - it is buried inside the 'ByteArrClassLoader' itself!

class RunTests
{
    // ********************************************************************************************
    // ********************************************************************************************
    // Run Method
    // ********************************************************************************************
    // ********************************************************************************************


    @SuppressWarnings({"unchecked", "rawtypes"})
    static void run(
            final ReadOnlyList<BuildPackage>    packagesIN,
            final String                        logDirName
        )
        throws IOException
    {
        final ByteArrClassLoader            bacl        = new ByteArrClassLoader();
        final StorageWriter                 log         = new StorageWriter();
        final ReadOnlyList<BuildPackage>    packages    = mustHaveTestFilesDir(packagesIN);

        final PkgTextBar textBar =
            new PkgTextBar(packages, "Testing Package", BGREEN_BKGND, BCYAN);

        log.sysOut = false;

        for (BuildPackage bp : packages)
        {
            final String logSubDirName =
                logDirName + bp.fullName + File.separator;

            final File logDir = new File(logSubDirName);

            if (! logDir.exists()) logDir.mkdirs();

            final ReadOnlyList<Class> testClasses = FileNode
                .createRoot(bp.pkgRootDirectory + "test-files" + File.separator)
                .loadTree(-1, FileNode.CLASS_FILES, null)
                .flattenJustFiles(RTC.FULLPATH_STREAM())
                .map((String fileName) -> (Class) bacl.readJavaClassNOIOE(fileName))
                .filter((Class c) -> TestingClass.class.isAssignableFrom(c))
                .collect(ReadOnlyArrayList.streamCollector());

            textBar.print(bp);

            for (final Class c : testClasses)
            {
                final String cName = c.getCanonicalName();
                System.out.println(BRED + "    Testing Class:  " + RESET + BGREEN + cName + RESET);

                final TestingClass tc;

                try
                    { tc = (TestingClass) c.getDeclaredConstructor().newInstance(); }

                catch (Exception | Error e)
                {
                    System.out.println(
                        EXCC.toString(e) + '\n' +
                        e.getMessage() + "\n\n" +
                        "For TestingClass:\n    " + BYELLOW + cName + RESET + '\n' +
                        "Unable to invoke the Zero-Argument Constructor for that Class.\n" +
                        "Did you remember to declare a public, Zero-Argument Constructor?\n"
                    );

                    System.exit(0); /* shhh compiler !! */ throw null;
                }
    

                final boolean   testResults     = tc.runTests(log);
                final String    logFileName     = logSubDirName + cName + ".txt";
                final String    htmlFileName    = logSubDirName + cName + ".html";
                final String    testOutput      = log.getString();
                final String    htmlOutput      = C.toHTML(testOutput, true, true, cName);

                log.erase();

                System.out.println("    Writing File:   " + BYELLOW + logFileName + RESET);
                FileRW.writeFile(testOutput, logFileName);

                System.out.println("    Writing File:   " + BYELLOW + htmlFileName + RESET);
                FileRW.writeFile(htmlOutput, htmlFileName);

                if (! testResults)
                {
                    System.out.println(
                        testOutput + '\n' +
                        BRED + "*** FAILED ***" + RESET
                    );

                    System.exit(-1);
                }
            }
        }

        System.out.println();
    }


    // ********************************************************************************************
    // ********************************************************************************************
    // Helper Method
    // ********************************************************************************************
    // ********************************************************************************************


    // All this does is filter out any packages which do not have a '../test-files/' subd-directory
    // The only reason this is necessary at all is somewhat because I have the capacity to get a 
    // little over-zealous with the "Read-Only" thing.  Otherewise, this would be several lines
    // shorter...

    private static ReadOnlyList<BuildPackage> mustHaveTestFilesDir 
        (final ReadOnlyList<BuildPackage> allPackages)
    {
        ROArrayListBuilder<BuildPackage> roalb = new ROArrayListBuilder<>();

        for (BuildPackage bp : allPackages)
        {
            final File testFilesDir =
                new File(bp.pkgRootDirectory + "test-files" + File.separator);

            if ((! testFilesDir.exists()) || (! testFilesDir.isDirectory()))
                continue;

            roalb.add(bp);
        }

        return roalb.build();
    }
}