Source Archaeology · Architecture Study

Test Generator 2.4.20

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.

Vendor: Fain & Company Product ver 2.4.20 VB6 + Java Servlets + MFC C++ 7,310 files Studied 2026-07-26

01What this actually is

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.

237k
lines of VB6 across 383 .frm/.bas/.cls/.ctl
43k
lines of Java across 244 source files
7.1k
lines of C++ (SecureTG + setup)
26
tables in the core schema
39
VB6 project files (.vbp) — 20+ shippable executables
40
Crystal Reports .rpt definitions
95
third-party DLL/OCX binaries vendored in external/
Provenance is unambiguous 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).

02The stack, end to end

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.

Authoring & administration desktop · vb6
TestGen.exemain console — 37 forms, 18 ActiveX tab modules
TGReports.exeCrystal Reports front end
Tools.exebackup / compact / repair
TGScan.exeTWAIN bubble-sheet scanner
MakeKey.exelicence key generator
TGAuth.exeLDAP / AD binding config
Test delivery three delivery channels
Tester.exeLAN client — 19 forms
Tester.exe (ST)stand-alone / disconnected mode
tester.war33 servlets on Tomcat
SecureTG.exekiosk shell wrapping the web tester
sTester / sTestGenlaunchers that self-update from the DB
tgadmin.war8 servlets — web-side admin
Shared domain library Shared_Modules · linked into every vb6 exe
DBClasses/TGTest, TGTaker, TGSummary, TGDetail, TGSection, TGLock, TGClass, TGMessage
CDAC_ADOsingle data-access façade, 5 connections
encryption/Blowfish, MD5, SHA-256, Radix-64
zip/Info-ZIP wrapper for test packaging
test_print/RTF paging & print preview
mail/SMTP + MX lookup
Persistence 5 logical databases · 6 engines
TestGentests, takers, scores, locks, logs
QBankquestion bank
Mediaimage/audio/video registry
Essaylong-form answers
Customcustomer-specific extensions

03Desktop tier — VB6

The desktop product is 20+ executables built from 39 .vbp manifests, all sharing one library directory. TestGen.vbp alone pulls in 127 source members.

Shippable executables

ProjectOutputVerRole (as declared in source)
MainMenuTestGen.exe2.4.20Author tests, manage takers/classes, run reports. Startup Sub Main, icon form frmLogin.
TesterTester.exe2.4.20Candidate client. Also hosts stand-alone mode (modST.bas, frmStandAlone.frm) and a SecureTG hand-off (secureTG.bas, frmSecureBG.frm).
TGReportsTGReports.exe2.4.20Crystal Reports runner. VersionProductName="TGReports 070115".
ToolsTools.exe2.4.20Database backup (frmBackup) and compact/repair (frmCompact).
scanningTGScan.exe2.4.20Optical mark reading. References Orion OTwainATL.dll + OBitmapATL.dll.
ST_Updatestupdate.exe2.4.20Pushes new tests into a deployed stand-alone tester.
TG_StartersTestGen.exe / sTester.exe1.0.7Thin launchers. Query the updatebin table for newer binaries, run the updater, self-register OCX/DLLs, then exec the real app.
Make_KeyMakeKey.exe1.0.0Licence key generator (vendor-side).
TGWebDemoTGWebDemo.exe1.0.0Winsock-only demo harness.
Customcustom.exe2.0.0Per-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.

The main console is built from tab modules

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:

FCOTG_TG

Test authoring

FCOTG_QBANK

Question bank

FCOTG_TAKER

Candidate records

FCOTG_USERS

Accounts & permissions

FCOTG_MEDIA

Image/audio/video assets

FCOTG_REPORTS

Report launcher

FCOTG_LOCKS

Exam locking

FCOTG_LOGS

Audit trail

FCOTG_SysControl

System options

FCOTG_TREEVIEW

Navigation tree

FCOTG_TQUICKP

Quick properties

FCOTG_APPHELP

In-app help

Six more generic controls (ucRTFEditor, ucEmailEditor, ucScoreMessage, ucDateControl, ucPrintSpecial, classes) provide reusable editing widgets.

A parallel Unicode fork exists 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.

04Data tier

One schema, portable across six engines, reached through a single façade class.

Engine portability

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
EngineProvider / driver usedNotes
Access 2000Microsoft.Jet.OLEDB.4.0Default. Four separate .mdb files under the install path, protected by Access user-level security via a shipped SYSTEM.MDW workgroup file.
SQL ServerMSOLEDBSQL, falling back to SQLOLEDB.1The modern-provider branch is a later retrofit over the original SQLOLEDB path.
MySQLODBC Driver={…}Driver name is configurable, not hard-coded.
PostgreSQLODBC Driver={…}Same pattern as MySQL.
OracleOraOLEDB.OracleMatching Tools/dbconvert/Oracle migration project.
FirebirdDeclared in the enum; no connection-string branch found. Aspirational.

Five connections, not one

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.

Configuration lives in the registry

' 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.

Core tables

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.

TableKey columnsPurpose
testsTST_IDTest definition — timing (TST_TIMED, TST_TIMELIMIT), retake policy, disclaimer, grade scale, question-bank mode, active window.
tg_questionsQ_ID, TST_IDQuestions bound to a test. Stores both plain text and RTF (Q_TEXT / Q_TEXTRTF), points, partial credit, image path and position.
qb_questionsThe reusable question bank, mirroring the same column shape.
takersTA_IDPeople. Two ID fields (TA_ID1/TA_ID2), credentials, TA_UserType (1 Admin / 2 Instructor / 3 Taker), TA_Permissions.
classesCL_IDCohorts — session, meeting time/days, semester, department, auto-enrol flag.
takers_classes
tests_classes
compositeEnrolment join tables.
summarySU_IDOne row per attempt: attempts, last score, objective score, start/end, time used, finished flag, corruption flag, serialised question-bank draw.
detailDE_ID, SU_IDOne row per answered question: answer, answer order, score, time spent, times answered, presentation order.
locksLK_IDExam locking with creation/update timestamps and a location string.
logs / log_typesAudit trail. TGLogMessage codes are structured: last digit 0 = addition, 3 = deletion, 5 = change, 9 = user-defined.
tst_st / standaloneTST_IDStand-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.

Question types

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:

Multiple Choice mc

CheckBoxes cb

True / False tf

Ordering o

Matching ma

Point and Click pac

Short Answer sa

Fill in the Blanks fitb

Essay essay

Likert likert

Numeric numeric

Test behaviour is heavily parameterised

05Web tier — Java on Tomcat

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.

common 20,353 lines

81 classes under com.fco.tg.base.* — entity model, DB pooling, templating, crypto, media.

tester 5,536 lines

33 servlets driving the candidate exam flow, plus three Quartz job classes.

tgadmin 2,784 lines

8 servlets: login, home, tests, test takers, courses, ad-hoc reports, unlock, password reset.

tg_majix 10,024 lines

Vendored com.tetrasix.majix — RTF→XML/HTML conversion, so RTF question text renders in a browser.

tg_wmfviewer 4,508 lines

Windows Metafile renderer — displays WMF clip art pasted into questions by desktop authors.

The exam flow, read off the servlet names

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

A hand-rolled template engine, used for HTML and SQL

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.

Runtime dependencies

31 jars are vendored under common/lib. The interesting ones:

JarWhat it's for
jasperreports 6.3.0 (+fonts, javaflow)Web-side reporting, the browser counterpart to desktop Crystal Reports.
iText 2.1.7 + iTextAsian + iTextXMLPDF output — certificates and reports.
poi 3.6, jxl 2.6.10Excel import/export.
quartz.jarScheduled jobs: ScheduleNotification, DeleteObsoleteLocks, ClearRTFCache.
jtds, sqljdbc, JSQLConnect, mysql-connector 5.1.22, postgresql 9.0-801, ojdbc14Six JDBC paths, matching the desktop tier's engine list.
ldap.jar, mail.jar, activation.jarDirectory authentication and SMTP.
groovy-all 1.5.5, commons-*, xercesImpl, JimiProScripting, utility, XML parsing, image decoding.
jakarta.servlet-api 5.0.0Notably modern — evidence the WAR was re-targeted at a much newer container than the Tomcat 4.1 in the original deployment notes.

Authentication

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.

06SecureTG — the kiosk layer

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.

Pre-flight refusals

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.

Lockdown

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.

Window control

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.

Config

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.

How the server knows the browser is SecureTG

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))
        …
}
This is a fixed, unsalted shared secret Both sides hash the same two constant strings. The resulting 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.

07The external/ archive

A 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."

SupplierComponentUsed for
CrystalDecisionsCRPE32, cpeaut32, P2B*, crxf_pdf, crxf_rtfCrystal Reports 7 engine + PDF/RTF export filters, driving the desktop report set.
Microsoftmsvbvm60, 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.
VideoSoftVSPRINT7, VSSPELL, VSTHES, VSVPORT7Print engine, spell-checker and thesaurus inside the question editor.
DataDynamicsActiveBar 2 (Actbar2.ocx)The toolbar/menu framework across TestGen, TGReports and TGScan.
OrionOTwainATL, OBitmapATL, keylib32TWAIN acquisition and bitmap handling for the scanner module.
MXMX.OCX — ATL control MthX, source includedMathematical equation editing (FontHeight, SaveStyleInfo, AutoSize…), consumed by other_development/equation.
chilkatsoftCkString.dllString/encoding helper referenced directly by TestGen.vbp.
Info-ZIP / zlibzip.exe, unzip-5.50 source, sfx-header, zlib.dllTest packaging and the self-extracting stand-alone test distributables.
TGauthenticode/Code-signing material, with 2008 and 2010 vintages.
Secret material is checked in 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.

08Build, packaging and release

Versioning is manual

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.

Installers are Inno Setup

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).

Client auto-update runs through the database

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.

Stand-alone (disconnected) delivery

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."

Documentation set

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".

09Assessment

Genuinely well-structured

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.

Deep domain modelling

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.

Duplicated surface area

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.

Dead and drifting corners

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.

Security debt

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.

Platform-bound

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.


Where to look first

QuestionRead 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