A structural reading of three source archives — TestGeneratorV2420, secureTG, and external — recovered from ftp.testshop.com. Everything below is derived from the files themselves: project manifests, DDL, servlet code, and installer scripts.
A commercial computer-based testing / assessment suite built by Fain & Company (testshop.com). It spans three generations of technology stacked on one shared database schema: a Visual Basic 6 desktop authoring & delivery product, a Java servlet web tier that serves the same tests over HTTP, and an MFC C++ kiosk browser that locks Windows down while a candidate sits an exam.
MainMenu/TestGen.vbp declares MajorVer=2 MinorVer=4 RevisionVer=20, VersionProductName="Test Generator", VersionCompanyName="Fain & Co.". tg2setup/about.txt reads "Test Generator and Stand-alone Tester by Fain & Company, www.testshop.com". Two VCS histories are embedded: CVS (:ext:rahim@testgen.dyndns.org:/home/cvsroot) and later Subversion (https://atus-fileserver:8443/svn/FCO).
Three client tiers, one schema. The desktop tier and the web tier are independent implementations that read and write the same tables — the Java code re-implements the VB6 domain model rather than calling into it.
The desktop product is 20+ executables built from 39 .vbp manifests, all sharing one library directory. TestGen.vbp alone pulls in 127 source members.
| Project | Output | Ver | Role (as declared in source) |
|---|---|---|---|
| MainMenu | TestGen.exe | 2.4.20 | Author tests, manage takers/classes, run reports. Startup Sub Main, icon form frmLogin. |
| Tester | Tester.exe | 2.4.20 | Candidate client. Also hosts stand-alone mode (modST.bas, frmStandAlone.frm) and a SecureTG hand-off (secureTG.bas, frmSecureBG.frm). |
| TGReports | TGReports.exe | 2.4.20 | Crystal Reports runner. VersionProductName="TGReports 070115". |
| Tools | Tools.exe | 2.4.20 | Database backup (frmBackup) and compact/repair (frmCompact). |
| scanning | TGScan.exe | 2.4.20 | Optical mark reading. References Orion OTwainATL.dll + OBitmapATL.dll. |
| ST_Update | stupdate.exe | 2.4.20 | Pushes new tests into a deployed stand-alone tester. |
| TG_Starter | sTestGen.exe / sTester.exe | 1.0.7 | Thin launchers. Query the updatebin table for newer binaries, run the updater, self-register OCX/DLLs, then exec the real app. |
| Make_Key | MakeKey.exe | 1.0.0 | Licence key generator (vendor-side). |
| TGWebDemo | TGWebDemo.exe | 1.0.0 | Winsock-only demo harness. |
| Custom | custom.exe | 2.0.0 | Per-customer shell; sibling dirs bcs/ chevron/ panhandle/ smyrna fire/ david_wolf/ hold bespoke exporters. |
Tools/ additionally contains 11 satellite VB projects: dbcheck, dbexport, dbselect, dbup2d8, tgup2d8, organizeusers, removelock, resetPass, getVersion, tgauth, tgauthenticode, plus dbconvert in four engine-specific flavours.
Rather than one monolithic MDI form, TestGen.exe composes 12 FCOTG_* ActiveX UserControls, one per functional area — this is the product's real feature map:
Test authoring
Question bank
Candidate records
Accounts & permissions
Image/audio/video assets
Report launcher
Exam locking
Audit trail
System options
Navigation tree
Quick properties
In-app help
Six more generic controls (ucRTFEditor, ucEmailEditor, ucScoreMessage, ucDateControl, ucPrintSpecial, classes) provide reusable editing widgets.
Unicode MainMenu/ and Unicode Tester/ are near-complete copies of the two flagship projects at the same version (2.4.20), diverging in a handful of forms — frmEnrollment is present in the ANSI build and absent from the Unicode one. This is a maintained double, not dead weight: it doubles the surface area of any change to the desktop tier.
One schema, portable across six engines, reached through a single façade class.
Shared_Modules/modTGEnums.bas declares the supported backends, and CDAC_ADO.cls emits a different connection string for each:
' modTGEnums.bas Public Enum eDatabaseEngineType eDT_Access2000 = 1 eDT_MSSQL2000 = 5 eDT_MySQL4 = 10 eDT_Postgres7 = 15 eDT_Firebird = 20 eDT_Oracle = 25 End Enum
| Engine | Provider / driver used | Notes |
|---|---|---|
| Access 2000 | Microsoft.Jet.OLEDB.4.0 | Default. Four separate .mdb files under the install path, protected by Access user-level security via a shipped SYSTEM.MDW workgroup file. |
| SQL Server | MSOLEDBSQL, falling back to SQLOLEDB.1 | The modern-provider branch is a later retrofit over the original SQLOLEDB path. |
| MySQL | ODBC Driver={…} | Driver name is configurable, not hard-coded. |
| PostgreSQL | ODBC Driver={…} | Same pattern as MySQL. |
| Oracle | OraOLEDB.Oracle | Matching Tools/dbconvert/Oracle migration project. |
| Firebird | — | Declared in the enum; no connection-string branch found. Aspirational. |
CDAC_ADO holds five independent ADO connections — pmoTGCon, pmoQBCon, pmoMediaCon, pmoEssayCon, pmoCustomCon. Under Access these map to four physical files (TestGen.MDB, QBank.MDB, Media.MDB, Essay.MDB); under any server engine all five collapse onto one database name. The split is a legacy of the Jet file-size ceiling, carried forward as a logical partition.
' modConstants.bas Public Const FCOTG_REG_ROOT = "SOFTWARE\FCO\Test Generator II"
Server, port, database name, username and password are all read from that key. Credentials are stored Blowfish-encrypted and decrypted at startup with a key assembled at runtime from character arithmetic (convertToBinary3(64) and friends) — obfuscation, not key management.
The canonical DDL is generated in code, once per engine, by Shared_Modules/ddl_mssql.cls and ddl_mysql.cls — 26 CREATE TABLE statements in the SQL Server generator, 27 in the MySQL one.
| Table | Key columns | Purpose |
|---|---|---|
| tests | TST_ID | Test definition — timing (TST_TIMED, TST_TIMELIMIT), retake policy, disclaimer, grade scale, question-bank mode, active window. |
| tg_questions | Q_ID, TST_ID | Questions bound to a test. Stores both plain text and RTF (Q_TEXT / Q_TEXTRTF), points, partial credit, image path and position. |
| qb_questions | — | The reusable question bank, mirroring the same column shape. |
| takers | TA_ID | People. Two ID fields (TA_ID1/TA_ID2), credentials, TA_UserType (1 Admin / 2 Instructor / 3 Taker), TA_Permissions. |
| classes | CL_ID | Cohorts — session, meeting time/days, semester, department, auto-enrol flag. |
| takers_classes tests_classes | composite | Enrolment join tables. |
| summary | SU_ID | One row per attempt: attempts, last score, objective score, start/end, time used, finished flag, corruption flag, serialised question-bank draw. |
| detail | DE_ID, SU_ID | One row per answered question: answer, answer order, score, time spent, times answered, presentation order. |
| locks | LK_ID | Exam locking with creation/update timestamps and a location string. |
| logs / log_types | — | Audit trail. TGLogMessage codes are structured: last digit 0 = addition, 3 = deletion, 5 = change, 9 = user-defined. |
| tst_st / standalone | TST_ID | Stand-alone deployment options — login required, save behaviour, exit behaviour — and the import ledger for tests shipped to disconnected machines. |
| mediabin / MEDIA logobin / updatebin | — | Binary payloads held in-database. updatebin is the software auto-update channel (see below). |
| Essay, topics, admin surveyorder, uld_history tmp_questions, tmp_answer, tmp_summary | — | Long-form answers, subject taxonomy, system settings, survey ordering, legacy-upgrade history, and scratch tables used during import. |
Q_TYPE is a free-text column; the authoritative list is enumerated in the Java entity Question.java, which maps each display name to a short code:
TGTestType).TGTestBankTypes: all questions, n random questions, n random subjects, n per subject, n subjects × m questions, or a per-subject count map.TGTestPassFailOption).A servlet application, deployed as WARs, that re-implements test delivery over HTTP against the same schema. Deployment notes reference jakarta-tomcat-4.1.30 fronted by Apache via mod_jk.
81 classes under com.fco.tg.base.* — entity model, DB pooling, templating, crypto, media.
33 servlets driving the candidate exam flow, plus three Quartz job classes.
8 servlets: login, home, tests, test takers, courses, ad-hoc reports, unlock, password reset.
Vendored com.tetrasix.majix — RTF→XML/HTML conversion, so RTF question text renders in a browser.
Windows Metafile renderer — displays WMF clip art pasted into questions by desktop authors.
LoginServlet / DualLoginServlet / SSOLogin / ForgetPassword / ResetPassword
→ TestListServlet → ViewDisclaimerServlet → BeginTestMessageServlet
→ BeginTestServlet → BeginSectionMessageServlet
→ LoadQuestionServlet / DispQuestionServlet
→ PointClickServlet · ViewQuestionMediaServlet · MediaBankServlet
→ ShowTimedServlet / ShowSectionTimedServlet
→ SectionTimeUpServlet / TimeUpServlet / SectionCriteriaEndServlet
→ EndSectionMessageServlet → EndTestMessageServlet
→ SummaryListServlet · TestReportServlet · ViewTakerSummaryServlet
→ CertificateServlet
Side channels: FeedBackServlet · UserFBServlet · OutEnrollServlet ·
UnlockServlet · SCORMLauncher · FlushCacheServlet
com.fco.tg.base.html.Tmpl implements an HTML::Template dialect — Var, If, Unless, Loop, Include, Conditional. The same parser drives 62 .tmpl page templates and 138 .sql query templates. Queries are assembled by string substitution with an escaping hint:
-- templates/common/sql/taker/… select ta_password, ta_email from takers where ta_username = '<tmpl_var escape=sql name=ta_username>' and ta_email = '<tmpl_var escape=sql name=ta_email>'
Safety therefore depends entirely on every author remembering escape=sql. There is no prepared-statement layer; SqlRunner executes the rendered string.
31 jars are vendored under common/lib. The interesting ones:
| Jar | What it's for |
|---|---|
| jasperreports 6.3.0 (+fonts, javaflow) | Web-side reporting, the browser counterpart to desktop Crystal Reports. |
| iText 2.1.7 + iTextAsian + iTextXML | PDF output — certificates and reports. |
| poi 3.6, jxl 2.6.10 | Excel import/export. |
| quartz.jar | Scheduled jobs: ScheduleNotification, DeleteObsoleteLocks, ClearRTFCache. |
| jtds, sqljdbc, JSQLConnect, mysql-connector 5.1.22, postgresql 9.0-801, ojdbc14 | Six JDBC paths, matching the desktop tier's engine list. |
| ldap.jar, mail.jar, activation.jar | Directory authentication and SMTP. |
| groovy-all 1.5.5, commons-*, xercesImpl, JimiPro | Scripting, utility, XML parsing, image decoding. |
| jakarta.servlet-api 5.0.0 | Notably modern — evidence the WAR was re-targeted at a much newer container than the Tomcat 4.1 in the original deployment notes. |
Three modes are declared in eAuthenticationType: Local, LDAP, WINNT. docs/ldap.txt documents Active Directory binding through the Global Catalog on port 3268, configured by TGAuth.exe, with the rule that the user must also exist in TG — AD verifies the password, TG owns authorisation. docs/sso.txt covers IIS integrated auth forwarded to Tomcat over the jk2 connector with tomcatAuthentication=false.
An MFC single-document application whose entire view is an embedded Internet Explorer IWebBrowser2 control pointed at the web tester. Its job is to make the Windows desktop unusable for the duration of the exam.
Exits with a message if launched inside Terminal Services (GetSystemMetrics(SM_REMOTESESSION)), or if outlook.exe is running — a crude second-screen/second-channel check. Both are overridable from the INI.
SecureTGDLL.dll installs a system-wide WH_KEYBOARD_LL hook (the Paul DiLascia MSDN pattern) and swallows Ctrl+Esc, Alt+Tab, Alt+Esc, the Windows keys, Ctrl+C/V/X/P and PrintScreen. Task Manager and the taskbar are disabled separately.
On start it broadcasts MIN_ALL (command 419) to Shell_TrayWnd to minimise everything, shows itself maximised, and sets SPI_SETSCREENSAVERRUNNING to suppress system key handling.
Every switch lives in SecureTG.ini under [SECURETG]: starturl, password_on_launch, enable_task_manager, enable_task_bar, enable_task_keys, enable_copy_paste, skip_outlook_running_check. Both keys and values are Blowfish-encrypted on disk, so the shipped INI is opaque hex. SecureTGSetup.exe is the little MFC dialog that writes it.
This is the one genuinely clever seam between the C++ and Java halves, and it is worth reading closely because it is also the weakest link.
SecureTG builds two strings from character arithmetic so they never appear as literals in the binary, MD5s each, and injects them as request headers:
// SecureTGView.cpp — reconstructed key = MD5("TGAdmin"); // built as 65+19, 65+6, 65+0, 65+35, … secure_user_agent = MD5("SecureTG"); // built as 65+18, 65+36, 65+34, 65+52, … header = "X-Tester: " + key + "\r\nUser-Agent: " + secure_user_agent + "\r\nAccept-Language: en-us";
The header is re-attached on every BeforeNavigate2, so it survives form posts inside the exam. On the other side, tester/LoginServlet.java checks it:
private static boolean verifyKeys(String useragent, String key) { // key = TGAdmin, useragent = SecureTG if (MD5.hash("TGAdmin").equals(key) && MD5.hash("SecureTG").equals(useragent)) … }
issecure flag is what gates secure-mode login — so anything that can set two HTTP headers is indistinguishable from a locked-down kiosk. There is also a backwards-compatibility fallback that accepts the Referer header in place of X-Tester, which widens it further.
A companion ActiveX control (web/secureTG/activex to pop securetg/source/activex/stglauncher.vbp) exists so a web page can launch the kiosk directly from the browser.
external/ archiveA vendored dependency store, one directory per supplier — 75 DLLs and 20 OCXs. Its README.txt states the policy plainly: "if we have the source code, we'll keep it here, and build it ourselves. if the supplier will only give us compiled versions, we'll keep the binaries here."
| Supplier | Component | Used for |
|---|---|---|
| CrystalDecisions | CRPE32, cpeaut32, P2B*, crxf_pdf, crxf_rtf | Crystal Reports 7 engine + PDF/RTF export filters, driving the desktop report set. |
| Microsoft | msvbvm60, DAO 3.6, MDAC/ADO, Jet 4.0, MFC42, mscomctl, richtx32, msflxgrd, … | The VB6 runtime and standard control set — shipped and self-registered by the installer. |
| VideoSoft | VSPRINT7, VSSPELL, VSTHES, VSVPORT7 | Print engine, spell-checker and thesaurus inside the question editor. |
| DataDynamics | ActiveBar 2 (Actbar2.ocx) | The toolbar/menu framework across TestGen, TGReports and TGScan. |
| Orion | OTwainATL, OBitmapATL, keylib32 | TWAIN acquisition and bitmap handling for the scanner module. |
| MX | MX.OCX — ATL control MthX, source included | Mathematical equation editing (FontHeight, SaveStyleInfo, AutoSize…), consumed by other_development/equation. |
| chilkatsoft | CkString.dll | String/encoding helper referenced directly by TestGen.vbp. |
| Info-ZIP / zlib | zip.exe, unzip-5.50 source, sfx-header, zlib.dll | Test packaging and the self-extracting stand-alone test distributables. |
| TG | authenticode/ | Code-signing material, with 2008 and 2010 vintages. |
external/TG/authenticode/ contains mykey.pvk alongside mycert.spc (plus .2008 and .2010 copies) — the Authenticode private key used to sign shipped binaries. Separately, web/data_source_notes.txt carries a live-looking JDBC username and password in plain text. Neither belongs in a distributable archive; treat both as compromised.
docs/versioning.txt, in full: "edit the .vbp file by incrementing the RevisionVer= line. duplicate the changes to that version in the version.txt file found in the same projects folder." There is no build server — output/ is the (empty) drop directory that every .vbp points at via Path32=..\output.
tg2setup/ holds 11 installer definitions — TestGen, Tester, ST, tgdemo, tgscan, tgup2d8, equation, scorm12, dbexport, and four dbconv* variants — each a thin .iss that includes five shared fragments from tg2setup/common/. Testgen.iss ships the VB6 runtime into {sys} with regserver, drops the eight executables, seeds the clean Access databases from Clean/ with onlyifdoesntexist, and installs the .rpt report set (40 report definitions live in the tree, 38 of them under other_development/reports).
This is the most unusual mechanism in the codebase. sTester.exe / sTestGen.exe are stubs that, before launching the real application:
select filename, create_date from updatebin where for_tg = true and create_date > <last update from registry> order by create_date desc
If rows come back, the launcher runs its update form to pull the new binaries out of the database, then walks a second query for rows flagged self_register and calls RegisterServer on each DLL/OCX. An administrator upgrades a LAN of clients by inserting rows into a table — no share, no MSI, no push agent.
docs/st-spec.html specifies the offline product in detail: install by web download, CD or "a set of 4 floppy disks"; tests arrive as self-extracting installers that locate and patch an existing ST; results are exported as an encrypted .MDB named LastName FirstName - TestName.MDB and returned to HQ for import. The spec is candid about its own limits — "Currently there is no procedure in place to ensure end users have not cheated."
docs/ carries 20+ written specs and notes — st-spec.html, lock-spec.html, install-spec.html, uninstall-spec.html, tester-login-spec.html, import-scores-spec.html, corrupt-data-spec.txt, access-security.txt, naming-spec.txt, doc-strategy.txt, an architecture.vsd Visio diagram, plus a 963-line changelog.txt keyed to CVS tags FCO-TestGen-B0 onward. Help was authored in RoboHelp as WinHelp, with a documented intent to move to HTML Help "for TG 3.0".
The Shared_Modules discipline is real: one CDAC_ADO data façade, one DBClasses row-mirror per interesting table (with a readme.txt stating exactly that rule), one enum module that the DDL is expected to track. For a VB6 product of this age that is unusually deliberate.
Eleven question types, six question-bank draw strategies, section-level timing and criteria, before/after messaging hooks, retake policies that branch on why the previous attempt ended. This is a mature assessment engine, not a quiz app.
ANSI and Unicode forks of both flagship VB projects; three parallel report stacks (Crystal on the desktop, JasperReports on the web, hand-built RTF/HTML in RTFBuilder/HTMLBuilder); the domain model expressed once in VB and again in Java. Every behavioural change has three to four homes.
eDT_Firebird has no implementation. other_development/ holds report variants explicitly named "not used". Build outputs are committed alongside sources under web/code/*/build/output, roughly doubling the Java file count.
Template-string SQL with opt-in escaping; a constant, unsalted MD5 pair as the kiosk's proof of identity, with a Referer fallback; Blowfish-with-a-derived-key used as credential storage; an Authenticode private key and a plaintext database password sitting in the tree.
VB6 runtime, DAO/Jet, MFC42, Crystal 7, an IE IWebBrowser2 host, TWAIN, and a lockdown model built on Shell_TrayWnd and low-level keyboard hooks. The desktop and kiosk tiers are pinned to a Windows generation that no longer ships. The Java tier is the only part with a plausible forward path — and its jakarta.servlet-api 5.0.0 jar suggests someone had already started walking it.
| Question | Read this |
|---|---|
| What does the desktop app do? | MainMenu/TestGen.vbp · MainMenu/FCOTG_*.ctl |
| What is the data model? | Shared_Modules/ddl_mssql.cls · Shared_Modules/modTGEnums.bas · Shared_Modules/DBClasses/ |
| How is the database reached? | Shared_Modules/DBClasses/CDAC_ADO.cls · CDAC_ADO_Params.cls |
| How does the web exam flow work? | web/code/tester/src/com/fco/tg/tester/*.java |
| How are queries built? | web/code/common/src/com/fco/tg/base/html/Tmpl/ · */templates/**/sql/ |
| How does the kiosk lock the machine? | secureTG/SecureTGDLL.cpp · SecurityMgr.h · SecureTGView.cpp |
| How is it shipped? | tg2setup/TestGen/Testgen.iss · tg2setup/common/*.iss |
| How do clients update? | TG_Starter/TGStarterMain.bas (updatebin) |
| What was the team thinking? | docs/changelog.txt · docs/*-spec.html · docs/access-security.txt |