• Publicity: Public Only All

aa-test-procs.tcl

Procs to support the acs-automated-testing package.

Location:
packages/acs-automated-testing/tcl/aa-test-procs.tcl
Created:
21 June 2001
Author:
Peter Harper <peter.harper@open-msg.com>

Procedures in this file

Detailed information

aa_call_component (public)

 aa_call_component component_id

Executes the chunk of code associated with the component_id.

Call this function from within a testcase body only.

Parameters:
component_id (required)
Author:
Peter Harper
Created:
28 October 2001

Partial Call Graph (max 5 caller/called nodes):
%3 test_db_check_news_archive db_check_news_archive (test news) aa_call_component aa_call_component test_db_check_news_archive->aa_call_component test_db_check_news_create db_check_news_create (test news) test_db_check_news_create->aa_call_component test_db_check_news_set_approve db_check_news_set_approve (test news) test_db_check_news_set_approve->aa_call_component test_db_check_news_status db_check_news_status (test news) test_db_check_news_status->aa_call_component aa_log aa_log (public) aa_call_component->aa_log

Testcases:
db_check_news_create, db_check_news_archive, db_check_news_set_approve, db_check_news_status

aa_check_leftovers (public)

 aa_check_leftovers [ -silent ] [ msg ]

# # Perform cleanup tests to check for object/command leaks in # either the called functions or in the test itself. #

Switches:
-silent (optional, boolean)
Parameters:
msg (optional, defaults to "final")

Partial Call Graph (max 5 caller/called nodes):
%3 test_webtest_example webtest_example (test acs-automated-testing) aa_check_leftovers aa_check_leftovers test_webtest_example->aa_check_leftovers aa_equals aa_equals (public) aa_check_leftovers->aa_equals aa_log aa_log (public) aa_check_leftovers->aa_log aa_used_application_memory aa_used_application_memory (private) aa_check_leftovers->aa_used_application_memory acs::icanuse acs::icanuse (public) aa_check_leftovers->acs::icanuse nsf::dispatch nsf::dispatch aa_check_leftovers->nsf::dispatch aa_run_testcase aa_run_testcase (private) aa_run_testcase->aa_check_leftovers

Testcases:
webtest_example

aa_display_result (public)

 aa_display_result -response response -explanation explanation

Displays either a pass or fail result with specified explanation depending on the given response.

Switches:
-response (required)
A boolean value where true (or 1, etc) corresponds to a pass result, otherwise the result is a fail.
-explanation (required)
An explanation accompanying the response.

Partial Call Graph (max 5 caller/called nodes):
%3 test_fs_add_file_to_folder fs_add_file_to_folder (test file-storage) aa_display_result aa_display_result test_fs_add_file_to_folder->aa_display_result test_fs_add_file_to_folder_twt fs_add_file_to_folder_twt (test file-storage) test_fs_add_file_to_folder_twt->aa_display_result aa_indent aa_indent (private) aa_display_result->aa_indent aa_log_result aa_log_result (public) aa_display_result->aa_log_result

Testcases:
fs_add_file_to_folder_twt, fs_add_file_to_folder

aa_equals (public)

 aa_equals affirm_name affirm_actual affirm_value

Tests that the affirm_actual is equal to affirm_value.

Call this function within a testcase, stub or component.

Parameters:
affirm_name (required)
affirm_actual (required)
affirm_value (required)
Returns:
True if the affirmation passed, false otherwise.
Author:
Peter Harper
Created:
24 July 2001

Partial Call Graph (max 5 caller/called nodes):
%3 test_aa__coverage_proc_coverage aa__coverage_proc_coverage (test acs-automated-testing) aa_equals aa_equals test_aa__coverage_proc_coverage->aa_equals test_aa__coverage_proc_coverage_level aa__coverage_proc_coverage_level (test acs-automated-testing) test_aa__coverage_proc_coverage_level->aa_equals test_aa__coverage_proc_proc_list_covered aa__coverage_proc_proc_list_covered (test acs-automated-testing) test_aa__coverage_proc_proc_list_covered->aa_equals test_webtest_example webtest_example (test acs-automated-testing) test_webtest_example->aa_equals aa_indent aa_indent (private) aa_equals->aa_indent aa_log_result aa_log_result (public) aa_equals->aa_log_result aa_check_leftovers aa_check_leftovers (public) aa_check_leftovers->aa_equals acs::test::xpath::equals acs::test::xpath::equals (public) acs::test::xpath::equals->aa_equals lang::test::assert_browser_locale lang::test::assert_browser_locale (private) lang::test::assert_browser_locale->aa_equals lang::test::check_import_result lang::test::check_import_result (private) lang::test::check_import_result->aa_equals lang::test::execute_upgrade lang::test::execute_upgrade (private) lang::test::execute_upgrade->aa_equals

Testcases:
webtest_example, aa__coverage_proc_coverage, aa__coverage_proc_proc_list_covered, aa__coverage_proc_coverage_level

aa_error (public)

 aa_error error_notes

Writes an error message to the testcase log.

Call this function within a testcase, stub or component.

Parameters:
error_notes (required)
Author:
Peter Harper
Created:
04 November 2001

Partial Call Graph (max 5 caller/called nodes):
%3 test_acs_subsite_expose_bug_775 acs_subsite_expose_bug_775 (test acs-subsite) aa_error aa_error test_acs_subsite_expose_bug_775->aa_error test_callgraph__bad_library_calls callgraph__bad_library_calls (test acs-api-browser) test_callgraph__bad_library_calls->aa_error test_callgraph__bad_page_calls callgraph__bad_page_calls (test acs-api-browser) test_callgraph__bad_page_calls->aa_error aa_log_result aa_log_result (public) aa_error->aa_log_result acs::test::user::create acs::test::user::create (public) acs::test::user::create->aa_error faq::twt::delete faq::twt::delete (private) faq::twt::delete->aa_error faq::twt::delete_Q_A faq::twt::delete_Q_A (private) faq::twt::delete_Q_A->aa_error faq::twt::disable_enable faq::twt::disable_enable (private) faq::twt::disable_enable->aa_error faq::twt::edit_Q_A faq::twt::edit_Q_A (private) faq::twt::edit_Q_A->aa_error

Testcases:
callgraph__bad_library_calls, callgraph__bad_page_calls, acs_subsite_expose_bug_775

aa_export_vars (public)

 aa_export_vars varnames

Called from an initialization class constructor or a component to explicitly export the specified variables to the current testcase. You need to call aa_export_vars before you create the variables. Example:

    aa_export_vars {package_id item_id}
    set package_id 23
    set item_id 109
    

Parameters:
varnames (required)

Partial Call Graph (max 5 caller/called nodes):
%3 test_db_check_news_archive db_check_news_archive (test news) aa_export_vars aa_export_vars test_db_check_news_archive->aa_export_vars test_db_check_news_create db_check_news_create (test news) test_db_check_news_create->aa_export_vars test_db_check_news_set_approve db_check_news_set_approve (test news) test_db_check_news_set_approve->aa_export_vars test_db_check_news_status db_check_news_status (test news) test_db_check_news_status->aa_export_vars xowf::test::require_test_instance xowf::test::require_test_instance (private) xowf::test::require_test_instance->aa_export_vars

Testcases:
db_check_news_create, db_check_news_archive, db_check_news_set_approve, db_check_news_status

aa_false (public)

 aa_false affirm_name affirm_expr

Tests that affirm_expr is false. Call this function within a testcase, stub or component.

Parameters:
affirm_name (required)
affirm_expr (required)
Returns:
True if the affirmation passed, false otherwise.
Author:
Peter Harper
Created:
24 July 2001

Partial Call Graph (max 5 caller/called nodes):
%3 test_webtest_example webtest_example (test acs-automated-testing) aa_false aa_false test_webtest_example->aa_false aa_indent aa_indent (private) aa_false->aa_indent aa_log_result aa_log_result (public) aa_false->aa_log_result acs::test::reply_contains_no acs::test::reply_contains_no (public) acs::test::reply_contains_no->aa_false cr_item_search::assert_not_in_queue cr_item_search::assert_not_in_queue (private) cr_item_search::assert_not_in_queue->aa_false packages/acs-core-docs/www/files/tutorial/myfirstpackage-procs.tcl packages/acs-core-docs/ www/files/tutorial/myfirstpackage-procs.tcl packages/acs-core-docs/www/files/tutorial/myfirstpackage-procs.tcl->aa_false workflow::test::run_bug_tracker_test workflow::test::run_bug_tracker_test (public) workflow::test::run_bug_tracker_test->aa_false workflow::test::run_with_teardown workflow::test::run_with_teardown (public) workflow::test::run_with_teardown->aa_false

Testcases:
webtest_example

aa_get_first_url (public)

 aa_get_first_url -package_key package_key

Procedure for getting the URL of a mounted package with the package_key. It uses the first instance that it founds. This is useful for tclwebtest tests.

Switches:
-package_key (required)

Partial Call Graph (max 5 caller/called nodes):
%3 test_fs_add_file_to_folder fs_add_file_to_folder (test file-storage) aa_get_first_url aa_get_first_url test_fs_add_file_to_folder->aa_get_first_url test_web_forum_edit web_forum_edit (test forums) test_web_forum_edit->aa_get_first_url test_web_forum_new web_forum_new (test forums) test_web_forum_new->aa_get_first_url test_web_forum_view web_forum_view (test forums) test_web_forum_view->aa_get_first_url test_web_forums_message_and_reply web_forums_message_and_reply (test forums) test_web_forums_message_and_reply->aa_get_first_url site_node::get_package_url site_node::get_package_url (public) aa_get_first_url->site_node::get_package_url site_node::instantiate_and_mount site_node::instantiate_and_mount (public) aa_get_first_url->site_node::instantiate_and_mount faq::twt::delete faq::twt::delete (private) faq::twt::delete->aa_get_first_url faq::twt::delete_Q_A faq::twt::delete_Q_A (private) faq::twt::delete_Q_A->aa_get_first_url faq::twt::disable_enable faq::twt::disable_enable (private) faq::twt::disable_enable->aa_get_first_url faq::twt::edit_Q_A faq::twt::edit_Q_A (private) faq::twt::edit_Q_A->aa_get_first_url faq::twt::edit_one faq::twt::edit_one (private) faq::twt::edit_one->aa_get_first_url

Testcases:
fs_add_file_to_folder, web_forum_new, web_forum_view, web_forum_edit, web_forums_message_and_reply

aa_log (public)

 aa_log [ args... ]

Writes a log message to the testcase log. Call this function within a testcase, stub or component.

Author:
Peter Harper
Created:
24 July 2001

Partial Call Graph (max 5 caller/called nodes):
%3 test_webtest_example webtest_example (test acs-automated-testing) aa_log aa_log test_webtest_example->aa_log aa_indent aa_indent (private) aa_log->aa_indent aa_log_result aa_log_result (public) aa_log->aa_log_result aa_call_component aa_call_component (public) aa_call_component->aa_log aa_check_leftovers aa_check_leftovers (public) aa_check_leftovers->aa_log aa_register_case aa_register_case (public) aa_register_case->aa_log acs::test::form_reply acs::test::form_reply (public) acs::test::form_reply->aa_log acs::test::http acs::test::http (public) acs::test::http->aa_log

Testcases:
webtest_example

aa_log_result (public)

 aa_log_result test_result [ args... ]

Log a test result

Parameters:
test_result (required)
Author:
Peter Harper
Created:
24 July 2001

Partial Call Graph (max 5 caller/called nodes):
%3 test_datamodel__acs_attribute_check datamodel__acs_attribute_check (test acs-tcl) aa_log_result aa_log_result test_datamodel__acs_attribute_check->aa_log_result test_datamodel__acs_object_type_check datamodel__acs_object_type_check (test acs-tcl) test_datamodel__acs_object_type_check->aa_log_result test_datamodel__named_constraints datamodel__named_constraints (test acs-tcl) test_datamodel__named_constraints->aa_log_result test_documentation__check_deprecated_see documentation__check_deprecated_see (test acs-tcl) test_documentation__check_deprecated_see->aa_log_result test_documentation__check_parameters documentation__check_parameters (test acs-tcl) test_documentation__check_parameters->aa_log_result aa_add_rollback_test aa_add_rollback_test (private) aa_log_result->aa_add_rollback_test aa_in_rollback_block_p aa_in_rollback_block_p (private) aa_log_result->aa_in_rollback_block_p db_dml db_dml (public) aa_log_result->db_dml aa_display_result aa_display_result (public) aa_display_result->aa_log_result aa_equals aa_equals (public) aa_equals->aa_log_result aa_error aa_error (public) aa_error->aa_log_result aa_false aa_false (public) aa_false->aa_log_result aa_log aa_log (public) aa_log->aa_log_result

Testcases:
datamodel__named_constraints, datamodel__acs_object_type_check, datamodel__acs_attribute_check, documentation__check_proc_doc, naming__proc_naming, documentation__check_deprecated_see, documentation__check_typos, documentation__check_parameters, files__check_info_files

aa_register_case (public)

 aa_register_case [ -libraries libraries ] [ -cats cats ] \
    [ -error_level error_level ] [ -bugs bugs ] [ -procs procs ] \
    [ -urls urls ] [ -init_classes init_classes ] \
    [ -on_error on_error ] testcase_id testcase_desc [ args... ]

Registers a testcase with the acs-automated-testing system. Whenever possible, cases that fail to register are replaced with 'metatest' log cases, so that the register-time errors are visible at test time. See the tutorial for examples.

Switches:
-libraries (optional)
A list of keywords of additional code modules to load. The entire test case will fail if any package is missing. Currently includes tclwebtest.
-cats (optional)
Properties of the test case. Must be zero or more of the following:
  • db: Tests the database directly
  • api: tests the Tcl API
  • web: tests HTTP interface
  • smoke: Minimal test to assure functionality and catch basic errors.
  • stress: Puts heavy load on server or creates large numbers of records. Intended to simulate maximal production load.
  • security_risk: May introduce a security risk.
  • populator: Creates sample data for future use.
  • production_safe: Can be used on a live production site, i.e. for sanity checking or keepalive purposes. Implies: no risk of adding or deleting data; no risk of crashing; minimal cpu/db/net load.
-error_level (optional, defaults to "error")
Force all test failures to this error level. One of
  • notice: Informative. Does not indicate an error.
  • warning: May indicate an problem. Example: a non-critical bug repro case that hasn't been fixed.
  • error: normal error
  • metatest: Indicates a problem with the test framework, execution, or reporting. Suggests that current test results may be invalid. Use this for test cases that test the tests. Also used, automatically, for errors sourcing test cases.
-bugs (optional)
A list of integers corresponding to openacs.org bug numbers which relate to this test case.
-procs (optional)
A list of OpenACS procs which are tested by this case.
-urls (optional)
A list of URLs (relative to package) tested in web test case
-init_classes (optional)
Deprecated.
-on_error (optional)
Deprecated.
Parameters:
testcase_id (required)
testcase_desc (required)
Author:
Peter Harper
Created:
24 July 2001

Partial Call Graph (max 5 caller/called nodes):
%3 test_aa__coverage_proc_coverage aa__coverage_proc_coverage (test acs-automated-testing) aa_register_case aa_register_case test_aa__coverage_proc_coverage->aa_register_case test_aa__coverage_proc_coverage_level aa__coverage_proc_coverage_level (test acs-automated-testing) test_aa__coverage_proc_coverage_level->aa_register_case test_aa__coverage_proc_proc_list_covered aa__coverage_proc_proc_list_covered (test acs-automated-testing) test_aa__coverage_proc_proc_list_covered->aa_register_case test_webtest_example webtest_example (test acs-automated-testing) test_webtest_example->aa_register_case aa_log aa_log (public) aa_register_case->aa_log aa_log_result aa_log_result (public) aa_register_case->aa_log_result api_add_to_proc_doc api_add_to_proc_doc (public) aa_register_case->api_add_to_proc_doc packages/acs-core-docs/www/files/tutorial/myfirstpackage-procs.tcl packages/acs-core-docs/ www/files/tutorial/myfirstpackage-procs.tcl packages/acs-core-docs/www/files/tutorial/myfirstpackage-procs.tcl->aa_register_case

Testcases:
webtest_example, aa__coverage_proc_coverage, aa__coverage_proc_proc_list_covered, aa__coverage_proc_coverage_level

aa_register_component (public)

 aa_register_component component_id component_desc body

Registers a reusable code component. Provide a component identifier, description and component body code.

This is useful for reusing code that sets up / clears down, data common to many testcases.

Parameters:
component_id (required)
component_desc (required)
body (required)
Author:
Peter Harper
Created:
28 October 2001

Partial Call Graph (max 5 caller/called nodes):
%3 test_db_check_news_archive db_check_news_archive (test news) aa_register_component aa_register_component test_db_check_news_archive->aa_register_component test_db_check_news_create db_check_news_create (test news) test_db_check_news_create->aa_register_component test_db_check_news_set_approve db_check_news_set_approve (test news) test_db_check_news_set_approve->aa_register_component test_db_check_news_status db_check_news_status (test news) test_db_check_news_status->aa_register_component

Testcases:
db_check_news_create, db_check_news_archive, db_check_news_set_approve, db_check_news_status

aa_register_init_class (public)

 aa_register_init_class init_class_id init_class_desc constructor \
    destructor

Registers an initialization class to be used by one or more testcases. An initialization class can be assigned to a testcase via the aa_register_case proc. An initialization constructor is called once before running a set of testcases, and the destructor called once upon completion of running a set of testcases.

The idea behind this is that it could be used to perform data intensive operations that shared amongst a set if testcases. For example, mounting an instance of a package. This could be performed by each testcase individually, but this would be highly inefficient if there are any significant number of them. Better to let the acs-automated-testing infrastructure call the init_class code to set the package up, run all the tests, then call the destructor to unmount the package.

Parameters:
init_class_id (required)
Unique string to identify the init class
init_class_desc (required)
Longer description of the init class
constructor (required)
Tcl code block to run to setup the init class
destructor (required)
Tcl code block to tear down the init class
Author:
Peter Harper
Created:
04 November 2001

Partial Call Graph (max 5 caller/called nodes):
%3 test_check_package_mount check_package_mount (test news) aa_register_init_class aa_register_init_class test_check_package_mount->aa_register_init_class test_db_check_news_archive db_check_news_archive (test news) test_db_check_news_archive->aa_register_init_class test_db_check_news_create db_check_news_create (test news) test_db_check_news_create->aa_register_init_class test_db_check_news_revision db_check_news_revision (test news) test_db_check_news_revision->aa_register_init_class test_db_check_news_set_approve db_check_news_set_approve (test news) test_db_check_news_set_approve->aa_register_init_class

Testcases:
check_package_mount, db_check_news_create, db_check_news_revision, db_check_news_archive, db_check_news_set_approve, db_check_news_status

aa_run_with_teardown (public)

 aa_run_with_teardown -test_code test_code \
    [ -teardown_code teardown_code ] [ -rollback ]

Execute code in test_code and guarantee that code in teardown_code will be executed even if error is thrown. Will catch errors in teardown_code as well and provide stack traces for both code blocks.

Switches:
-test_code (required)
Tcl code that sets up the test case and executes tests
-teardown_code (optional)
Tcl code that tears down database data etc. that needs to execute after testing even if error is thrown.
-rollback (optional, boolean)
If specified, any db transactions in test_code will be rolled back.
Author:
Peter Marklund

Partial Call Graph (max 5 caller/called nodes):
%3 test_webtest_example webtest_example (test acs-automated-testing) aa_run_with_teardown aa_run_with_teardown test_webtest_example->aa_run_with_teardown aa_end_rollback_block aa_end_rollback_block (private) aa_run_with_teardown->aa_end_rollback_block aa_execute_rollback_tests aa_execute_rollback_tests (private) aa_run_with_teardown->aa_execute_rollback_tests aa_start_rollback_block aa_start_rollback_block (private) aa_run_with_teardown->aa_start_rollback_block db_transaction db_transaction (public) aa_run_with_teardown->db_transaction navigation::test::context_bar_multirow_filter navigation::test::context_bar_multirow_filter (private) navigation::test::context_bar_multirow_filter->aa_run_with_teardown packages/acs-core-docs/www/files/tutorial/myfirstpackage-procs.tcl packages/acs-core-docs/ www/files/tutorial/myfirstpackage-procs.tcl packages/acs-core-docs/www/files/tutorial/myfirstpackage-procs.tcl->aa_run_with_teardown

Testcases:
webtest_example

aa_section (public)

 aa_section log_notes

Writes a log message indicating a new section to the log files.

Parameters:
log_notes (required)

Partial Call Graph (max 5 caller/called nodes):
%3 test_webtest_example webtest_example (test acs-automated-testing) aa_section aa_section test_webtest_example->aa_section aa_log_result aa_log_result (public) aa_section->aa_log_result

Testcases:
webtest_example

aa_silence_log_entries (public)

 aa_silence_log_entries -severities severities code

Silence expected messages in the system log. The proc deactivates the specified severity levels during the code in the last argument is executed. After it has finished, the severity levels are reset to their previous values.

Switches:
-severities (required)
Parameters:
code (required)

Partial Call Graph (max 5 caller/called nodes):
%3 test_upgrade upgrade (test acs-lang) aa_silence_log_entries aa_silence_log_entries test_upgrade->aa_silence_log_entries test_webtest_example webtest_example (test acs-automated-testing) test_webtest_example->aa_silence_log_entries lang::test::execute_upgrade lang::test::execute_upgrade (private) lang::test::execute_upgrade->aa_silence_log_entries

Testcases:
webtest_example, upgrade

aa_stub (public)

 aa_stub proc_name new_body

Stubs a function. Provide the procedure name and the new body code.

Either call this function from within a testcase for a testcase specific stub, or outside a testcase for a file-wide stub.

Parameters:
proc_name (required)
new_body (required)
Author:
Peter Harper
Created:
24 July 2001

Partial Call Graph (max 5 caller/called nodes):
%3 test_acs_admin_check_expired_certificates acs_admin_check_expired_certificates (test acs-admin) aa_stub aa_stub test_acs_admin_check_expired_certificates->aa_stub test_auth_email_on_password_change auth_email_on_password_change (test acs-authentication) test_auth_email_on_password_change->aa_stub test_auth_password_change auth_password_change (test acs-authentication) test_auth_password_change->aa_stub test_auth_password_recover auth_password_recover (test acs-authentication) test_auth_password_recover->aa_stub test_auth_password_reset auth_password_reset (test acs-authentication) test_auth_password_reset->aa_stub aa_proc_copy aa_proc_copy aa_stub->aa_proc_copy aa_run_testcase aa_run_testcase (private) aa_run_testcase->aa_stub workflow::test::run_bug_tracker_test workflow::test::run_bug_tracker_test (public) workflow::test::run_bug_tracker_test->aa_stub

Testcases:
acs_admin_check_expired_certificates, auth_password_change, auth_password_recover, auth_password_reset, auth_use_email_for_login_p, auth_email_on_password_change, util__replace_temporary_tags_with_lookups

aa_test::parse_install_file (public)

 aa_test::parse_install_file -path path -array array

Processes the xml report outputted from install.sh for display.

Switches:
-path (required)
-array (required)

Partial Call Graph (max 5 caller/called nodes):
%3 packages/acs-automated-testing/www/index.tcl packages/acs-automated-testing/ www/index.tcl aa_test::parse_install_file aa_test::parse_install_file packages/acs-automated-testing/www/index.tcl->aa_test::parse_install_file packages/acs-automated-testing/www/server.tcl packages/acs-automated-testing/ www/server.tcl packages/acs-automated-testing/www/server.tcl->aa_test::parse_install_file export_vars export_vars (public) aa_test::parse_install_file->export_vars template::util::read_file template::util::read_file (public) aa_test::parse_install_file->template::util::read_file util::interval_pretty util::interval_pretty (public) aa_test::parse_install_file->util::interval_pretty xml_doc_get_first_node xml_doc_get_first_node (public) aa_test::parse_install_file->xml_doc_get_first_node xml_node_get_attribute xml_node_get_attribute (public) aa_test::parse_install_file->xml_node_get_attribute

Testcases:
No testcase defined.

aa_test::parse_test_file (public)

 aa_test::parse_test_file -path path -array array

Processes the xml report with test result data for display.

Switches:
-path (required)
-array (required)

Partial Call Graph (max 5 caller/called nodes):
%3 packages/acs-automated-testing/www/index.tcl packages/acs-automated-testing/ www/index.tcl aa_test::parse_test_file aa_test::parse_test_file packages/acs-automated-testing/www/index.tcl->aa_test::parse_test_file packages/acs-automated-testing/www/server.tcl packages/acs-automated-testing/ www/server.tcl packages/acs-automated-testing/www/server.tcl->aa_test::parse_test_file template::util::read_file template::util::read_file (public) aa_test::parse_test_file->template::util::read_file xml_doc_get_first_node xml_doc_get_first_node (public) aa_test::parse_test_file->xml_doc_get_first_node xml_node_get_attribute xml_node_get_attribute (public) aa_test::parse_test_file->xml_node_get_attribute xml_node_get_children_by_name xml_node_get_children_by_name (public) aa_test::parse_test_file->xml_node_get_children_by_name xml_node_get_content xml_node_get_content (public) aa_test::parse_test_file->xml_node_get_content

Testcases:
No testcase defined.

aa_test::xml_report_dir (public)

 aa_test::xml_report_dir

Retrieves the XMLReportDir parameter.

Returns:
Returns the value for the XMLReportDir parameter.

Partial Call Graph (max 5 caller/called nodes):
%3 aa_test::write_test_file aa_test::write_test_file (private) aa_test::xml_report_dir aa_test::xml_report_dir aa_test::write_test_file->aa_test::xml_report_dir packages/acs-automated-testing/www/index.tcl packages/acs-automated-testing/ www/index.tcl packages/acs-automated-testing/www/index.tcl->aa_test::xml_report_dir parameter::get parameter::get (public) aa_test::xml_report_dir->parameter::get

Testcases:
No testcase defined.

aa_test_running_p (public)

 aa_test_running_p

Check, if the regression test is currently running.

Returns:
boolean value indicating state

Partial Call Graph (max 5 caller/called nodes):
%3 test_webtest_example webtest_example (test acs-automated-testing) aa_test_running_p aa_test_running_p test_webtest_example->aa_test_running_p ad_dom_sanitize_html ad_dom_sanitize_html (public) ad_dom_sanitize_html->aa_test_running_p api_proc_documentation api_proc_documentation (public) api_proc_documentation->aa_test_running_p apm_read_package_info_file apm_read_package_info_file (public) apm_read_package_info_file->aa_test_running_p callback callback (public) callback->aa_test_running_p sec_handler sec_handler (private) sec_handler->aa_test_running_p

Testcases:
webtest_example

aa_test_start (public)

 aa_test_start

Set the start flag of the regression test case.

Partial Call Graph (max 5 caller/called nodes):
%3 test_webtest_example webtest_example (test acs-automated-testing) aa_test_start aa_test_start test_webtest_example->aa_test_start aa_runseries aa_runseries (private) aa_runseries->aa_test_start acs::test::login acs::test::login (public) acs::test::login->aa_test_start sec_handler sec_handler (private) sec_handler->aa_test_start

Testcases:
webtest_example

aa_true (public)

 aa_true affirm_name affirm_expr

Tests that affirm_expr is true.

Call this function within a testcase, stub or component.

Parameters:
affirm_name (required)
affirm_expr (required)
Returns:
True if the affirmation passed, false otherwise.
Author:
Peter Harper
Created:
24 July 2001

Partial Call Graph (max 5 caller/called nodes):
%3 test_aa__coverage_proc_coverage aa__coverage_proc_coverage (test acs-automated-testing) aa_true aa_true test_aa__coverage_proc_coverage->aa_true test_aa__coverage_proc_proc_list_covered aa__coverage_proc_proc_list_covered (test acs-automated-testing) test_aa__coverage_proc_proc_list_covered->aa_true test_webtest_example webtest_example (test acs-automated-testing) test_webtest_example->aa_true aa_indent aa_indent (private) aa_true->aa_indent aa_log_result aa_log_result (public) aa_true->aa_log_result acs::test::find_link acs::test::find_link (public) acs::test::find_link->aa_true acs::test::reply_contains acs::test::reply_contains (public) acs::test::reply_contains->aa_true acs::test::reply_has_status_code acs::test::reply_has_status_code (public) acs::test::reply_has_status_code->aa_true acs::test::xpath::equals acs::test::xpath::equals (public) acs::test::xpath::equals->aa_true acs::test::xpath::non_empty acs::test::xpath::non_empty (public) acs::test::xpath::non_empty->aa_true

Testcases:
webtest_example, aa__coverage_proc_coverage, aa__coverage_proc_proc_list_covered

aa_unstub (public)

 aa_unstub proc_name

Copies (back) a proc with "_unstubbed" suffix to its supposedly unpostfixed original name.

Parameters:
proc_name (required)
Author:
Peter Harper
Created:
24 July 2001

Partial Call Graph (max 5 caller/called nodes):
%3 test_util__replace_temporary_tags_with_lookups util__replace_temporary_tags_with_lookups (test acs-lang) aa_unstub aa_unstub test_util__replace_temporary_tags_with_lookups->aa_unstub aa_proc_copy aa_proc_copy aa_unstub->aa_proc_copy aa_run_testcase aa_run_testcase (private) aa_run_testcase->aa_unstub workflow::test::run_bug_tracker_test workflow::test::run_bug_tracker_test (public) workflow::test::run_bug_tracker_test->aa_unstub

Testcases:
util__replace_temporary_tags_with_lookups

acs::test::confirm_email (public)

 acs::test::confirm_email -user_id user_id

Confirms user email

Switches:
-user_id (required)

Partial Call Graph (max 5 caller/called nodes):
%3 test_acs_subsite_test_email_confirmation acs_subsite_test_email_confirmation (test acs-subsite) acs::test::confirm_email acs::test::confirm_email test_acs_subsite_test_email_confirmation->acs::test::confirm_email test_webtest_example webtest_example (test acs-automated-testing) test_webtest_example->acs::test::confirm_email acs::test::http acs::test::http (public) acs::test::confirm_email->acs::test::http acs::test::reply_has_status_code acs::test::reply_has_status_code (public) acs::test::confirm_email->acs::test::reply_has_status_code auth::get_user_secret_token auth::get_user_secret_token (public) acs::test::confirm_email->auth::get_user_secret_token export_vars export_vars (public) acs::test::confirm_email->export_vars party::get party::get (public) acs::test::confirm_email->party::get

Testcases:
webtest_example, acs_subsite_test_email_confirmation

acs::test::dom_html (public)

 acs::test::dom_html var html body

Parses HTML into a tDOM object and executes some code.

Parameters:
var (required)
the variable name that body can refer to as documentElement of the document (e.g. "root").
html (required)
the markup to be parsed.
body (required)
a Tcl script executed in the caller scope that can assume the document to be parsed and be available in "var".

Partial Call Graph (max 5 caller/called nodes):
%3 test_create_folder_with_page create_folder_with_page (test xowf) acs::test::dom_html acs::test::dom_html test_create_folder_with_page->acs::test::dom_html test_create_form_with_form_instance create_form_with_form_instance (test xowiki) test_create_form_with_form_instance->acs::test::dom_html test_create_form_with_numeric create_form_with_numeric (test xowiki) test_create_form_with_numeric->acs::test::dom_html test_form_validate form_validate (test xowiki) test_form_validate->acs::test::dom_html test_link_tests link_tests (test xowiki) test_link_tests->acs::test::dom_html dom dom acs::test::dom_html->dom acs::test::find_link acs::test::find_link (public) acs::test::find_link->acs::test::dom_html acs::test::get_form acs::test::get_form (public) acs::test::get_form->acs::test::dom_html file_storage::test::delete_first_file file_storage::test::delete_first_file (private) file_storage::test::delete_first_file->acs::test::dom_html xowf::test::question_names_from_input_form xowf::test::question_names_from_input_form (private) xowf::test::question_names_from_input_form->acs::test::dom_html xowiki::test::create_form xowiki::test::create_form (public) xowiki::test::create_form->acs::test::dom_html

Testcases:
markup_parsing, create_folder_with_page, link_tests, create_form_with_form_instance, create_form_with_numeric, form_validate, nested_self_references

acs::test::find_link (public)

 acs::test::find_link -last_request last_request [ -user_id user_id ] \
    [ -base base ] [ -label label ]

Find the first link based on the provided label and return the href.

Switches:
-last_request (required)
-user_id (optional, defaults to "0")
-base (optional, defaults to "/")
-label (optional)
Author:
Gustaf Neumann

Partial Call Graph (max 5 caller/called nodes):
%3 test_fs_add_file_to_folder fs_add_file_to_folder (test file-storage) acs::test::find_link acs::test::find_link test_fs_add_file_to_folder->acs::test::find_link aa_true aa_true (public) acs::test::find_link->aa_true acs::test::detail_link acs::test::detail_link (private) acs::test::find_link->acs::test::detail_link acs::test::dom_html acs::test::dom_html (public) acs::test::find_link->acs::test::dom_html acs::test::follow_link acs::test::follow_link (public) acs::test::follow_link->acs::test::find_link

Testcases:
fs_add_file_to_folder

acs::test::follow_link (public)

 acs::test::follow_link -last_request last_request [ -user_id user_id ] \
    [ -base base ] [ -label label ]

Follow the first provided label and return the page info. Probably, we want as well other mechanisms to locate the anchor element later.

Switches:
-last_request (required)
-user_id (optional, defaults to "0")
-base (optional, defaults to "/")
-label (optional)
Author:
Gustaf Neumann

Partial Call Graph (max 5 caller/called nodes):
%3 test_fs_add_file_to_folder fs_add_file_to_folder (test file-storage) acs::test::follow_link acs::test::follow_link test_fs_add_file_to_folder->acs::test::follow_link test_fs_create_folder fs_create_folder (test file-storage) test_fs_create_folder->acs::test::follow_link test_fs_edit_folder fs_edit_folder (test file-storage) test_fs_edit_folder->acs::test::follow_link acs::test::find_link acs::test::find_link (public) acs::test::follow_link->acs::test::find_link acs::test::http acs::test::http (public) acs::test::follow_link->acs::test::http file_storage::test::add_file_to_folder file_storage::test::add_file_to_folder (private) file_storage::test::add_file_to_folder->acs::test::follow_link file_storage::test::create_new_folder file_storage::test::create_new_folder (private) file_storage::test::create_new_folder->acs::test::follow_link file_storage::test::delete_current_folder file_storage::test::delete_current_folder (private) file_storage::test::delete_current_folder->acs::test::follow_link file_storage::test::edit_folder file_storage::test::edit_folder (private) file_storage::test::edit_folder->acs::test::follow_link forums::test::new_postings forums::test::new_postings (private) forums::test::new_postings->acs::test::follow_link

Testcases:
fs_create_folder, fs_edit_folder, fs_add_file_to_folder

acs::test::form_get_fields (public)

 acs::test::form_get_fields form

Get the fields from a form.

Parameters:
form (required)
Author:
Gustaf Neumann
See Also:

Partial Call Graph (max 5 caller/called nodes):
%3 test_create_folder_with_page create_folder_with_page (test xowf) acs::test::form_get_fields acs::test::form_get_fields test_create_folder_with_page->acs::test::form_get_fields test_create_form_with_form_instance create_form_with_form_instance (test xowiki) test_create_form_with_form_instance->acs::test::form_get_fields test_create_workflow_with_instance create_workflow_with_instance (test xowf) test_create_workflow_with_instance->acs::test::form_get_fields test_markup_parsing markup_parsing (test acs-automated-testing) test_markup_parsing->acs::test::form_get_fields acs::test::form_reply acs::test::form_reply (public) acs::test::form_reply->acs::test::form_get_fields acs::test::login acs::test::login (public) acs::test::login->acs::test::form_get_fields file_storage::test::add_file_to_folder file_storage::test::add_file_to_folder (private) file_storage::test::add_file_to_folder->acs::test::form_get_fields xowiki::test::create_form_page xowiki::test::create_form_page (public) xowiki::test::create_form_page->acs::test::form_get_fields

Testcases:
markup_parsing, create_folder_with_page, create_workflow_with_instance, create_form_with_form_instance

acs::test::form_is_empty (public)

 acs::test::form_is_empty form

Check, if the form is empty

Parameters:
form (required)
Author:
Gustaf Neumann
See Also:

Partial Call Graph (max 5 caller/called nodes):
%3 test_markup_parsing markup_parsing (test acs-automated-testing) acs::test::form_is_empty acs::test::form_is_empty test_markup_parsing->acs::test::form_is_empty

Testcases:
markup_parsing

acs::test::form_reply (public)

 acs::test::form_reply [ -user_id user_id ] \
    [ -last_request last_request ] [ -form form ] [ -url url ] \
    [ -update update ] [ -remove remove ] [ form_content ]

Send a (POST) request to the specified URL based on the provided form_content which has the form of a dict. For convenience the update fields are provided to overload the form_content.

Switches:
-user_id (optional, defaults to "0")
-last_request (optional)
pass optionally the past request, from which cookie and login-info can be taken
-form (optional)
-url (optional)
-update (optional)
key/attribute list of values to be updated in the form content
-remove (optional)
keys to be removed from the form content
Parameters:
form_content (optional)

Partial Call Graph (max 5 caller/called nodes):
%3 test_create_folder_with_page create_folder_with_page (test xowf) acs::test::form_reply acs::test::form_reply test_create_folder_with_page->acs::test::form_reply test_create_form_with_form_instance create_form_with_form_instance (test xowiki) test_create_form_with_form_instance->acs::test::form_reply test_fs_add_file_to_folder fs_add_file_to_folder (test file-storage) test_fs_add_file_to_folder->acs::test::form_reply aa_log aa_log (public) acs::test::form_reply->aa_log acs::test::form_get_fields acs::test::form_get_fields (public) acs::test::form_reply->acs::test::form_get_fields acs::test::http acs::test::http (public) acs::test::form_reply->acs::test::http util::http::post_payload util::http::post_payload (public) acs::test::form_reply->util::http::post_payload acs::test::login acs::test::login (public) acs::test::login->acs::test::form_reply file_storage::test::create_new_folder file_storage::test::create_new_folder (private) file_storage::test::create_new_folder->acs::test::form_reply file_storage::test::delete_current_folder file_storage::test::delete_current_folder (private) file_storage::test::delete_current_folder->acs::test::form_reply file_storage::test::delete_first_file file_storage::test::delete_first_file (private) file_storage::test::delete_first_file->acs::test::form_reply file_storage::test::edit_folder file_storage::test::edit_folder (private) file_storage::test::edit_folder->acs::test::form_reply

Testcases:
fs_add_file_to_folder, create_folder_with_page, create_form_with_form_instance

acs::test::form_set_fields (public)

 acs::test::form_set_fields form fields

Set the fields in a form.

Parameters:
form (required)
fields (required)
Author:
Gustaf Neumann
See Also:

Partial Call Graph (max 5 caller/called nodes):
%3 test_create_form_with_form_instance create_form_with_form_instance (test xowiki) acs::test::form_set_fields acs::test::form_set_fields test_create_form_with_form_instance->acs::test::form_set_fields test_create_workflow_with_instance create_workflow_with_instance (test xowf) test_create_workflow_with_instance->acs::test::form_set_fields acs::test::login acs::test::login (public) acs::test::login->acs::test::form_set_fields xowiki::test::create_form xowiki::test::create_form (public) xowiki::test::create_form->acs::test::form_set_fields

Testcases:
create_workflow_with_instance, create_form_with_form_instance

acs::test::get_form (public)

 acs::test::get_form body xpath

Locate the HTML forms matching the XPath expression and retrieve its HTML attributes and the formfields in form of a Tcl dict. This is a convenience function, combining acs::test::dom_html and ::acs::test::xpath::get_form.

Parameters:
body (required)
xpath (required)
Returns:
Tcl dict with form attributes (starting with "@" and fields)
Author:
Gustaf Neumann
See Also:
  • acs::test::dom_html ::acs::test::xpath::get_form

Partial Call Graph (max 5 caller/called nodes):
%3 test_create_folder_with_page create_folder_with_page (test xowf) acs::test::get_form acs::test::get_form test_create_folder_with_page->acs::test::get_form test_create_form_with_form_instance create_form_with_form_instance (test xowiki) test_create_form_with_form_instance->acs::test::get_form test_markup_parsing markup_parsing (test acs-automated-testing) test_markup_parsing->acs::test::get_form acs::test::dom_html acs::test::dom_html (public) acs::test::get_form->acs::test::dom_html acs::test::xpath::get_form acs::test::xpath::get_form (public) acs::test::get_form->acs::test::xpath::get_form acs::test::login acs::test::login (public) acs::test::login->acs::test::get_form file_storage::test::add_file_to_folder file_storage::test::add_file_to_folder (private) file_storage::test::add_file_to_folder->acs::test::get_form file_storage::test::create_new_folder file_storage::test::create_new_folder (private) file_storage::test::create_new_folder->acs::test::get_form file_storage::test::delete_current_folder file_storage::test::delete_current_folder (private) file_storage::test::delete_current_folder->acs::test::get_form file_storage::test::delete_first_file file_storage::test::delete_first_file (private) file_storage::test::delete_first_file->acs::test::get_form

Testcases:
markup_parsing, create_folder_with_page, create_form_with_form_instance

acs::test::get_url_from_location (public)

 acs::test::get_url_from_location dict

Determine the URL based on the location field provided from the result dict (as returned from acs::test::http).

Parameters:
dict (required)
dict containing an ns_set called headers
See Also:

Partial Call Graph (max 5 caller/called nodes):
%3 test_create_folder_with_page create_folder_with_page (test xowf) acs::test::get_url_from_location acs::test::get_url_from_location test_create_folder_with_page->acs::test::get_url_from_location test_create_form_with_form_instance create_form_with_form_instance (test xowiki) test_create_form_with_form_instance->acs::test::get_url_from_location test_fs_add_file_to_folder fs_add_file_to_folder (test file-storage) test_fs_add_file_to_folder->acs::test::get_url_from_location file_storage::test::add_file_to_folder file_storage::test::add_file_to_folder (private) file_storage::test::add_file_to_folder->acs::test::get_url_from_location file_storage::test::create_new_folder file_storage::test::create_new_folder (private) file_storage::test::create_new_folder->acs::test::get_url_from_location file_storage::test::edit_folder file_storage::test::edit_folder (private) file_storage::test::edit_folder->acs::test::get_url_from_location xowiki::test::create_form xowiki::test::create_form (public) xowiki::test::create_form->acs::test::get_url_from_location xowiki::test::create_form_page xowiki::test::create_form_page (public) xowiki::test::create_form_page->acs::test::get_url_from_location

Testcases:
fs_add_file_to_folder, create_folder_with_page, create_form_with_form_instance

acs::test::http (public)

 acs::test::http [ -user_id user_id ] [ -user_info user_info ] \
    [ -last_request last_request ] [ -method method ] [ -body body ] \
    [ -timeout timeout ] [ -depth depth ] [ -headers headers ] \
    [ -prefix prefix ] [ -verbose ] [ -basic_auth ] request

Run an HTTP request against the actual server inside test cases.

Switches:
-user_id (optional, defaults to "0")
-user_info (optional)
-last_request (optional)
-method (optional, defaults to "GET")
-body (optional)
-timeout (optional, defaults to "10")
-depth (optional, defaults to "1")
follow redirects up to specified depth. Default means redirects won't be followed.
-headers (optional)
-prefix (optional)
-verbose (optional, boolean, defaults to "true")
-basic_auth (optional, boolean, defaults to "false")
Parameters:
request (required)
Author:
Gustaf Neumann

Partial Call Graph (max 5 caller/called nodes):
%3 test_ad_context_bar_multirow ad_context_bar_multirow (test acs-tcl) acs::test::http acs::test::http test_ad_context_bar_multirow->acs::test::http test_create_form_with_form_instance create_form_with_form_instance (test xowiki) test_create_form_with_form_instance->acs::test::http test_create_form_with_numeric create_form_with_numeric (test xowiki) test_create_form_with_numeric->acs::test::http test_form_validate form_validate (test xowiki) test_form_validate->acs::test::http test_front_page_1 front_page_1 (test acs-tcl) test_front_page_1->acs::test::http aa_log aa_log (public) acs::test::http->aa_log acs::test::set_user acs::test::set_user (private) acs::test::http->acs::test::set_user acs::test::url acs::test::url (public) acs::test::http->acs::test::url acs::test::confirm_email acs::test::confirm_email (public) acs::test::confirm_email->acs::test::http acs::test::follow_link acs::test::follow_link (public) acs::test::follow_link->acs::test::http acs::test::form_reply acs::test::form_reply (public) acs::test::form_reply->acs::test::http acs::test::login acs::test::login (public) acs::test::login->acs::test::http acs::test::logout acs::test::logout (public) acs::test::logout->acs::test::http

Testcases:
webtest_example, password_recovery_page, front_page_1, ad_context_bar_multirow, create_form_with_form_instance, create_form_with_numeric, form_validate, nested_self_references

acs::test::login (public)

 acs::test::login user_info

Login (register operation) in a web session

Parameters:
user_info (required)
dict containing at least email, last_name, username and password

Partial Call Graph (max 5 caller/called nodes):
%3 test_webtest_example webtest_example (test acs-automated-testing) acs::test::login acs::test::login test_webtest_example->acs::test::login aa_log aa_log (public) acs::test::login->aa_log aa_test_start aa_test_start (public) acs::test::login->aa_test_start acs::test::form_get_fields acs::test::form_get_fields (public) acs::test::login->acs::test::form_get_fields acs::test::form_reply acs::test::form_reply (public) acs::test::login->acs::test::form_reply acs::test::form_set_fields acs::test::form_set_fields (public) acs::test::login->acs::test::form_set_fields acs::test::set_user acs::test::set_user (private) acs::test::set_user->acs::test::login

Testcases:
webtest_example

acs::test::logout (public)

 acs::test::logout -last_request last_request

Logout from the current web session

Switches:
-last_request (required)
reply dict containing cookies

Partial Call Graph (max 5 caller/called nodes):
%3 test_webtest_example webtest_example (test acs-automated-testing) acs::test::logout acs::test::logout test_webtest_example->acs::test::logout aa_test_end aa_test_end (private) acs::test::logout->aa_test_end acs::test::http acs::test::http (public) acs::test::logout->acs::test::http acs::test::reply_has_status_code acs::test::reply_has_status_code (public) acs::test::logout->acs::test::reply_has_status_code

Testcases:
webtest_example

acs::test::reply_contains (public)

 acs::test::reply_contains [ -prefix prefix ] dict string

Convenience function for test cases to check, whether the resulting page contains the given string.

Switches:
-prefix (optional)
prefix for logging
Parameters:
dict (required)
request reply dict, containing at least the request body
string (required)
string to be checked on the page

Partial Call Graph (max 5 caller/called nodes):
%3 test_webtest_example webtest_example (test acs-automated-testing) acs::test::reply_contains acs::test::reply_contains test_webtest_example->acs::test::reply_contains aa_true aa_true (public) acs::test::reply_contains->aa_true acs::test::detail_link acs::test::detail_link (private) acs::test::reply_contains->acs::test::detail_link file_storage::test::add_file_to_folder file_storage::test::add_file_to_folder (private) file_storage::test::add_file_to_folder->acs::test::reply_contains file_storage::test::create_new_folder file_storage::test::create_new_folder (private) file_storage::test::create_new_folder->acs::test::reply_contains file_storage::test::edit_folder file_storage::test::edit_folder (private) file_storage::test::edit_folder->acs::test::reply_contains forums::test::edit forums::test::edit (private) forums::test::edit->acs::test::reply_contains forums::test::new_postings forums::test::new_postings (private) forums::test::new_postings->acs::test::reply_contains

Testcases:
webtest_example

acs::test::reply_contains_no (public)

 acs::test::reply_contains_no [ -prefix prefix ] dict string

Convenience function for test cases to check, whether the resulting page does not contain the given string.

Switches:
-prefix (optional)
prefix for logging
Parameters:
dict (required)
request reply dict, containing at least the request body
string (required)
string to be checked on the page

Partial Call Graph (max 5 caller/called nodes):
%3 test_webtest_example webtest_example (test acs-automated-testing) acs::test::reply_contains_no acs::test::reply_contains_no test_webtest_example->acs::test::reply_contains_no aa_false aa_false (public) acs::test::reply_contains_no->aa_false acs::test::detail_link acs::test::detail_link (private) acs::test::reply_contains_no->acs::test::detail_link forums::test::edit forums::test::edit (private) forums::test::edit->acs::test::reply_contains_no forums::test::new forums::test::new (private) forums::test::new->acs::test::reply_contains_no forums::test::new_postings forums::test::new_postings (private) forums::test::new_postings->acs::test::reply_contains_no

Testcases:
webtest_example

acs::test::reply_has_status_code (public)

 acs::test::reply_has_status_code [ -prefix prefix ] dict status_code

Convenience function for test cases to check, whether the reply has the given status code.

Switches:
-prefix (optional)
prefix for logging
Parameters:
dict (required)
request reply dict, containing at least the request status
status_code (required)
expected HTTP status codes

Partial Call Graph (max 5 caller/called nodes):
%3 test_ad_context_bar_multirow ad_context_bar_multirow (test acs-tcl) acs::test::reply_has_status_code acs::test::reply_has_status_code test_ad_context_bar_multirow->acs::test::reply_has_status_code test_create_form_with_form_instance create_form_with_form_instance (test xowiki) test_create_form_with_form_instance->acs::test::reply_has_status_code test_create_form_with_numeric create_form_with_numeric (test xowiki) test_create_form_with_numeric->acs::test::reply_has_status_code test_form_validate form_validate (test xowiki) test_form_validate->acs::test::reply_has_status_code test_front_page_1 front_page_1 (test acs-tcl) test_front_page_1->acs::test::reply_has_status_code aa_true aa_true (public) acs::test::reply_has_status_code->aa_true acs::test::detail_link acs::test::detail_link (private) acs::test::reply_has_status_code->acs::test::detail_link acs::test::confirm_email acs::test::confirm_email (public) acs::test::confirm_email->acs::test::reply_has_status_code acs::test::login acs::test::login (public) acs::test::login->acs::test::reply_has_status_code acs::test::logout acs::test::logout (public) acs::test::logout->acs::test::reply_has_status_code file_storage::test::add_file_to_folder file_storage::test::add_file_to_folder (private) file_storage::test::add_file_to_folder->acs::test::reply_has_status_code file_storage::test::create_new_folder file_storage::test::create_new_folder (private) file_storage::test::create_new_folder->acs::test::reply_has_status_code

Testcases:
webtest_example, password_recovery_page, front_page_1, ad_context_bar_multirow, create_form_with_form_instance, create_form_with_numeric, form_validate, nested_self_references

acs::test::require_package_instance (public)

 acs::test::require_package_instance -package_key package_key \
    [ -instance_name instance_name ] [ -empty ]

Returns a test instance of specified package_key mounted under specified name. Will create it if it is not found. It is currently assumed the instance will be mounted under the main subsite.

Switches:
-package_key (required)
package to be instantiated
-instance_name (optional)
name of the site-node this instance will be mounted to. Will default to -test
-empty (optional, boolean)
require an empty instance. If an existing instance is found, it will be deleted. If a package different than is found, it won't be deleted and the proc will return an error
Returns:
a package_id

Partial Call Graph (max 5 caller/called nodes):
%3 test_create_form_with_form_instance create_form_with_form_instance (test xowiki) acs::test::require_package_instance acs::test::require_package_instance test_create_form_with_form_instance->acs::test::require_package_instance test_create_form_with_numeric create_form_with_numeric (test xowiki) test_create_form_with_numeric->acs::test::require_package_instance test_form_validate form_validate (test xowiki) test_form_validate->acs::test::require_package_instance test_includelet_childresources includelet_childresources (test xowiki) test_includelet_childresources->acs::test::require_package_instance test_includelet_toc includelet_toc (test xowiki) test_includelet_toc->acs::test::require_package_instance apm_package_key_from_id apm_package_key_from_id (public) acs::test::require_package_instance->apm_package_key_from_id db_0or1row db_0or1row (public) acs::test::require_package_instance->db_0or1row site_node::delete site_node::delete (public) acs::test::require_package_instance->site_node::delete site_node::get_element site_node::get_element (public) acs::test::require_package_instance->site_node::get_element site_node::instantiate_and_mount site_node::instantiate_and_mount (public) acs::test::require_package_instance->site_node::instantiate_and_mount xowf::test::require_test_instance xowf::test::require_test_instance (private) xowf::test::require_test_instance->acs::test::require_package_instance

Testcases:
package_normalize_path, includelet_toc, includelet_childresources, link_tests, slot_interactions, path_resolve, create_form_with_form_instance, create_form_with_numeric, form_validate, nested_self_references

acs::test::url (public)

 acs::test::url
Returns:
the test URL representing our system for testing. This would normally look like the output of ns_conn location, unless it was overridden via the TestURL parameter in this package.

Partial Call Graph (max 5 caller/called nodes):
%3 test_webtest_example webtest_example (test acs-automated-testing) acs::test::url acs::test::url test_webtest_example->acs::test::url apm_package_id_from_key apm_package_id_from_key (public) acs::test::url->apm_package_id_from_key parameter::get parameter::get (public) acs::test::url->parameter::get acs::test::http acs::test::http (public) acs::test::http->acs::test::url packages/acs-core-docs/www/files/tutorial/myfirstpackage-procs.tcl packages/acs-core-docs/ www/files/tutorial/myfirstpackage-procs.tcl packages/acs-core-docs/www/files/tutorial/myfirstpackage-procs.tcl->acs::test::url twt::do_request twt::do_request (public) twt::do_request->acs::test::url twt::user::login twt::user::login (public) twt::user::login->acs::test::url twt::user::logout twt::user::logout (public) twt::user::logout->acs::test::url

Testcases:
webtest_example

acs::test::user::create (public)

 acs::test::user::create [ -admin ] [ -email email ] [ -locale locale ] \
    [ -password password ] [ -user_id user_id ]

Create a test user with random email and password for testing. If an email is passed in and the party identified by the password exists, the user_id of this party is returned in the dict.

Switches:
-admin (optional, boolean)
provide this switch to make the user site-wide admin
-email (optional)
email for the user to be created
-locale (optional, defaults to "en_US")
locale for the user to be created
-password (optional)
password for the user to be created
-user_id (optional)
user_id for the user to be created
Returns:
The user_info dict returned by auth::create_user. Contains the additional keys email and password.

Partial Call Graph (max 5 caller/called nodes):
%3 test_acs_admin_merge_MergeUserInfo acs_admin_merge_MergeUserInfo (test acs-admin) acs::test::user::create acs::test::user::create test_acs_admin_merge_MergeUserInfo->acs::test::user::create test_acs_subsite_check_composite_group acs_subsite_check_composite_group (test acs-subsite) test_acs_subsite_check_composite_group->acs::test::user::create test_acs_subsite_expose_bug_1144 acs_subsite_expose_bug_1144 (test acs-subsite) test_acs_subsite_expose_bug_1144->acs::test::user::create test_acs_subsite_test_email_confirmation acs_subsite_test_email_confirmation (test acs-subsite) test_acs_subsite_test_email_confirmation->acs::test::user::create test_ad_proc_change_state_member ad_proc_change_state_member (test acs-tcl) test_ad_proc_change_state_member->acs::test::user::create aa_error aa_error (public) acs::test::user::create->aa_error aa_log aa_log (public) acs::test::user::create->aa_log acs_magic_object acs_magic_object (public) acs::test::user::create->acs_magic_object acs_user::get acs_user::get (public) acs::test::user::create->acs_user::get ad_generate_random_string ad_generate_random_string (public) acs::test::user::create->ad_generate_random_string packages/acs-core-docs/www/files/tutorial/myfirstpackage-procs.tcl packages/acs-core-docs/ www/files/tutorial/myfirstpackage-procs.tcl packages/acs-core-docs/www/files/tutorial/myfirstpackage-procs.tcl->acs::test::user::create twt::user::create twt::user::create (public, deprecated) twt::user::create->acs::test::user::create

Testcases:
acs_admin_merge_MergeUserInfo, auth_authenticate, auth_create_user, auth_password_change, auth_password_reset, auth_email_on_password_change, webtest_example, acs_subsite_expose_bug_1144, acs_subsite_check_composite_group, acs_subsite_test_email_confirmation, community_cc_procs, demote_promote_a_user, ad_proc_change_state_member, ad_proc_permission_grant_and_revoke, ad_proc_permission_permission_p, calendar_basic_api, create_form_with_form_instance, create_form_with_numeric, form_validate, nested_self_references

acs::test::user::delete (public)

 acs::test::user::delete -user_id user_id \
    [ -delete_created_acs_objects ]

Remove a test user.

Switches:
-user_id (required)
-delete_created_acs_objects (optional, boolean, defaults to "false")

Partial Call Graph (max 5 caller/called nodes):
%3 test_webtest_example webtest_example (test acs-automated-testing) acs::test::user::delete acs::test::user::delete test_webtest_example->acs::test::user::delete acs_user::delete acs_user::delete (public) acs::test::user::delete->acs_user::delete db_dml db_dml (public) acs::test::user::delete->db_dml packages/acs-core-docs/www/files/tutorial/myfirstpackage-procs.tcl packages/acs-core-docs/ www/files/tutorial/myfirstpackage-procs.tcl packages/acs-core-docs/www/files/tutorial/myfirstpackage-procs.tcl->acs::test::user::delete

Testcases:
webtest_example

acs::test::visualize_control_chars (public)

 acs::test::visualize_control_chars lines

Quotes and therefore makes visible control chars in input lines

Parameters:
lines (required)

Partial Call Graph (max 5 caller/called nodes):
%3 test_visualize_control_chars visualize_control_chars (test acs-automated-testing) acs::test::visualize_control_chars acs::test::visualize_control_chars test_visualize_control_chars->acs::test::visualize_control_chars

Testcases:
visualize_control_chars

acs::test::xpath::equals (public)

 acs::test::xpath::equals node pairs

Test whether provided selectors (first element of the pair) return the specified results (second element of the pair).

Parameters:
node (required)
pairs (required)

Partial Call Graph (max 5 caller/called nodes):
%3 test_markup_parsing markup_parsing (test acs-automated-testing) acs::test::xpath::equals acs::test::xpath::equals test_markup_parsing->acs::test::xpath::equals aa_equals aa_equals (public) acs::test::xpath::equals->aa_equals aa_true aa_true (public) acs::test::xpath::equals->aa_true acs::test::xpath::get_text acs::test::xpath::get_text (public) acs::test::xpath::equals->acs::test::xpath::get_text

Testcases:
markup_parsing

acs::test::xpath::get_form (public)

 acs::test::xpath::get_form node xpath

Locate the HTML forms matching the XPath expression and retrieve its HTML attributes and the formfields in form of a Tcl dict.

Parameters:
node (required)
xpath (required)
Returns:
Tcl dict with form attributes (keys starting with "@", and entry "fields")
Author:
Gustaf Neumann

Partial Call Graph (max 5 caller/called nodes):
%3 test_create_folder_with_page create_folder_with_page (test xowf) acs::test::xpath::get_form acs::test::xpath::get_form test_create_folder_with_page->acs::test::xpath::get_form test_create_form_with_form_instance create_form_with_form_instance (test xowiki) test_create_form_with_form_instance->acs::test::xpath::get_form test_markup_parsing markup_parsing (test acs-automated-testing) test_markup_parsing->acs::test::xpath::get_form acs::test::xpath::get_form_values acs::test::xpath::get_form_values (public) acs::test::xpath::get_form->acs::test::xpath::get_form_values acs::test::get_form acs::test::get_form (public) acs::test::get_form->acs::test::xpath::get_form xowiki::test::create_form_page xowiki::test::create_form_page (public) xowiki::test::create_form_page->acs::test::xpath::get_form

Testcases:
markup_parsing, create_folder_with_page, create_form_with_form_instance

acs::test::xpath::get_form_values (public)

 acs::test::xpath::get_form_values node xpath

Obtain form values (input fields and textareas) in form of a dict (attribute value pairs). The provided XPath expression must point to the HTML form containing the values to be extracted.

Parameters:
node (required)
xpath (required)

Partial Call Graph (max 5 caller/called nodes):
%3 test_create_folder_with_page create_folder_with_page (test xowf) acs::test::xpath::get_form_values acs::test::xpath::get_form_values test_create_folder_with_page->acs::test::xpath::get_form_values test_create_form_with_form_instance create_form_with_form_instance (test xowiki) test_create_form_with_form_instance->acs::test::xpath::get_form_values acs::test::xpath::get_name_attribute acs::test::xpath::get_name_attribute (private) acs::test::xpath::get_form_values->acs::test::xpath::get_name_attribute acs::test::xpath::get_form acs::test::xpath::get_form (public) acs::test::xpath::get_form->acs::test::xpath::get_form_values xowiki::test::get_form_values xowiki::test::get_form_values (private) xowiki::test::get_form_values->acs::test::xpath::get_form_values

Testcases:
create_folder_with_page, create_form_with_form_instance

acs::test::xpath::get_text (public)

 acs::test::xpath::get_text root xpath

Get a text element from tdom via XPath expression. If the XPath expression matches multiple nodes, return a list.

Parameters:
root (required)
xpath (required)

Partial Call Graph (max 5 caller/called nodes):
%3 test_markup_parsing markup_parsing (test acs-automated-testing) acs::test::xpath::get_text acs::test::xpath::get_text test_markup_parsing->acs::test::xpath::get_text acs::test::xpath::equals acs::test::xpath::equals (public) acs::test::xpath::equals->acs::test::xpath::get_text acs::test::xpath::non_empty acs::test::xpath::non_empty (public) acs::test::xpath::non_empty->acs::test::xpath::get_text

Testcases:
markup_parsing

acs::test::xpath::non_empty (public)

 acs::test::xpath::non_empty node selectors

Test if provided selectors return nonempty results

Parameters:
node (required)
selectors (required)

Partial Call Graph (max 5 caller/called nodes):
%3 test_markup_parsing markup_parsing (test acs-automated-testing) acs::test::xpath::non_empty acs::test::xpath::non_empty test_markup_parsing->acs::test::xpath::non_empty aa_true aa_true (public) acs::test::xpath::non_empty->aa_true acs::test::xpath::get_text acs::test::xpath::get_text (public) acs::test::xpath::non_empty->acs::test::xpath::get_text xowiki::test::create_form_page xowiki::test::create_form_page (public) xowiki::test::create_form_page->acs::test::xpath::non_empty

Testcases:
markup_parsing
[ hide source ] | [ make this the default ]

Content File Source

##############################################################################
#
#   Copyright 2001, OpenACS, Peter Harper.
#
#   This file is part of acs-automated-testing
#
##############################################################################

ad_library {
    Procs to support the acs-automated-testing package.

    @author Peter Harper (peter.harper@open-msg.com)
    @creation-date 21 June 2001
}

#
# LARS: We do this here, because if we do it in the -init file, then
# we cannot register test cases in -procs files of packages.
#
if { ![nsv_exists aa_test cases] } {
    nsv_set aa_test cases {}
    nsv_set aa_test components {}
    nsv_set aa_test init_classes {}
    nsv_set aa_test categories { config db api web smoke stress security_risk populator production_safe }
    nsv_set aa_test exclusion_categories { stress security_risk }
    if {[parameter::get_from_package_key \
             -package_key "acs-automated-testing" \
             -parameter "SeleniumRcServer"] ne ""
    } {
        nsv_lappend aa_test categories "selenium"
    } else {
        nsv_lappend aa_test exclusion_categories "selenium"
    }
}

proc aa_proc_copy {proc_name_old proc_name_new {new_body ""}} {
    #
    # This is a single proc handling all stub management requirements
    # from aa-testing. Since the arglist nsf::procs is not simply "args"
    # (like for proc based ad_procs), but the real argument/parameter
    # list, we address these differences here for all needed cases.
    #
    if {[info procs $proc_name_old] ne ""} {
        #
        # We copy a regular Tcl proc
        #
        set args {}
        foreach arg [info args $proc_name_old] {
            if { [info default $proc_name_old $arg default_value] } {
                lappend args [list $arg $default_value]
            } else {
                lappend args $arg
            }
        }
        set old_body [info body $proc_name_old]
        if {$new_body eq ""} {
            set new_body $old_body
        }
        set arg_parser "[namespace tail $proc_name_old]__arg_parser"
        #
        # In case an arg-parser was used in the old body, but is
        # missing in the new version, add it automatically to the new
        # body.
        #
        if {[string match "*$arg_parser*" $old_body]} {
            if {![string match "*$arg_parser*" $new_body]} {
                set new_body $arg_parser\n$new_body
                #ns_log notice "... auto added arg_parser for '$proc_name_new' ====> new_body $new_body"
            }
        }
        ::proc $proc_name_new $args $new_body
    } elseif {$::acs::useNsfProc && [namespace which $proc_name_old] ne ""} {
        #
        # We copy a nsf::proc
        #
        # Use an absolute name to reference to a nsf::proc
        # unambiguously
        #
        set proc_name [namespace which $proc_name_old]
        if {$new_body eq ""} {
            set new_body [::nsf::cmd::info body $proc_name]
        }
        nsf::proc -ad $proc_name_new \
            [::nsf::cmd::info parameter $proc_name] \
            $new_body
    } else {
        error "no such proc $proc_name_old"
    }
}

d_proc -public aa_stub {
    proc_name
    new_body
} {
    Stubs a function.  Provide the procedure name and the new body code.
    <p>
    Either call this function from within a testcase for a testcase specific
    stub, or outside a testcase for a file-wide stub.

    @author Peter Harper
    @creation-date 24 July 2001
} {
    if {[info exists ::aa_testcase_id]} {
        #
        # Runtime testcase stub.
        # If a stub for this procedure hasn't already been defined, take a copy
        # of the original procedure and add it to the ::aa_stub_names list.
        #
        if {$proc_name ni $::aa_stub_names} {
            lappend ::aa_stub_names $proc_name
            aa_proc_copy $proc_name ${proc_name}_unstubbed
        }
        set ::aa_stub_sequence($proc_name) 1

        aa_proc_copy $proc_name $proc_name "
      global aa_stub_sequence
      global aa_testcase_id
      set sequence_id \$::aa_stub_sequence\($proc_name\)
      incr ::aa_stub_sequence\($proc_name\)
      $new_body
    "
        return
    } else {
        #
        # File wide stub.
        #
        if {![nsv_exists aa_file_wide_stubs [info script]]} {
            nsv_set aa_file_wide_stubs [info script] {}
        }
        nsv_lappend aa_file_wide_stubs [info script] [list $proc_name $new_body]
    }
}

d_proc -public aa_unstub {
    proc_name
} {
    Copies (back) a proc with "_unstubbed" suffix to its supposedly
    unpostfixed original name.

    @author Peter Harper
    @creation-date 24 July 2001
} {
    aa_proc_copy ${proc_name}_unstubbed $proc_name
    return
}

d_proc -public aa_register_init_class {
    init_class_id
    init_class_desc
    constructor
    destructor
} {
    Registers an initialization class to be used by one or more testcases.  An
    initialization class can be assigned to a testcase via the
    aa_register_case proc.

    An initialization constructor is called <strong>once</strong> before
    running a set of testcases, and the destructor called <strong>once</strong>
    upon completion of running a set of testcases.<p>
    The idea behind this is that it could be used to perform data intensive
    operations that shared amongst a set if testcases.  For example, mounting
    an instance of a package.  This could be performed by each testcase
    individually, but this would be highly inefficient if there are any
    significant number of them.

    Better to let the acs-automated-testing infrastructure call
    the init_class code to set the package up, run all the tests, then call
    the destructor to unmount the package.

    @author Peter Harper
    @creation-date 04 November 2001

    @param init_class_id Unique string to identify the init class
    @param init_class_desc Longer description of the init class
    @param constructor Tcl code block to run to setup the init class
    @param destructor Tcl code block to tear down the init class
} {
    #
    # Work out the package key
    #
    set package_root [file join $::acs::rootdir packages]
    set package_rel [string replace [info script] \
                         0 [string length $package_root]]
    if {![info exists package_key]} {
        set package_key [lindex [file split $package_rel] 0]
    }
    #
    # First, search the current list of init_classes. If an old version already
    # exists, replace it with the new version.
    #
    set lpos 0
    set found_pos -1
    foreach init_class [nsv_get aa_test init_classes] {
        if {[lindex $init_class 0] == $init_class_id &&
            [lindex $init_class 1] == $package_key} {
            nsv_set aa_test init_classes [lreplace [nsv_get aa_test init_classes] \
                                              $lpos $lpos \
                                              [list $init_class_id $package_key \
                                                   $init_class_desc \
                                                   [info script] \
                                                   $constructor $destructor]]
            set found_pos $lpos
            break
        }
        incr lpos
    }
    #
    # If we haven't already replaced an existing entry, append the new
    # entry to the list.
    #
    if {$found_pos == -1} {
        nsv_lappend aa_test init_classes [list $init_class_id $package_key \
                                              $init_class_desc \
                                              [info script] \
                                              $constructor $destructor]
    }

    #
    # Define the functions.  Note the destructor upvars into the
    # aa_runseries function to gain visibility of all the variables
    # the constructor has exported.
    #
    d_proc -private _${package_key}__i_$init_class_id {} "
    aa_log \"Running \\\"$init_class_id\\\" initialization class constructor\"
    $constructor
  "
    d_proc -private _${package_key}__d_$init_class_id {} "
    upvar _aa_exports _aa_exports
    foreach v \$_aa_exports(\[list $package_key $init_class_id\]) {
      upvar \$v \$v
    }
    $destructor
  "
}

d_proc -public aa_register_component {
    component_id
    component_desc
    body
} {
    Registers a reusable code component.  Provide a component identifier,
    description and component body code.
    <p>
    This is useful for reusing code that sets up / clears down, data common
    to many testcases.
    @author Peter Harper
    @creation-date 28 October 2001
} {
    #
    # Work out the package key
    #
    set package_root [file join $::acs::rootdir packages]
    set package_rel [string replace [info script] \
                         0 [string length $package_root]]
    set package_key [lindex [file split $package_rel] 0]
    #
    # First, search the current list of components. If an old version already
    # exists, replace it with the new version.
    #
    set lpos 0
    set found_pos -1
    foreach component [nsv_get aa_test components] {
        if {[lindex $component 0] == $component_id &&
            [lindex $component 1] == $package_key} {
            nsv_set aa_test components [lreplace [nsv_get aa_test components] \
                                            $lpos $lpos \
                                            [list $component_id $package_key \
                                                 $component_desc \
                                                 [info script] \
                                                 $body]]
            set found_pos $lpos
            break
        }
        incr lpos
    }
    #
    # If we haven't already replaced an existing entry, append the new
    # entry to the list.
    #
    if {$found_pos == -1} {
        nsv_lappend aa_test components [list $component_id $package_key \
                                            $component_desc \
                                            [info script] \
                                            $body]
    }

    #  set munged_body [subst {uplevel 1 {$body}}]
    d_proc -private _${package_key}__c_$component_id {} $body
}

d_proc -public aa_call_component {
    component_id
} {
    Executes the chunk of code associated with the component_id.  <p>
    Call this function from within a testcase body only.
    @author Peter Harper
    @creation-date 28 October 2001
} {
    set body ""

    #
    # Search for the component body
    #
    foreach component [nsv_get aa_test components] {
        if {$component_id == [lindex $component 0] &&
            $::aa_package_key  == [lindex $component 1]} {
            set body [lindex $component 4]
        }
    }

    #
    # If the component exists, execute the body code in the testcases stack
    # level.
    #
    if {$body ne ""} {
        aa_log "Running component $component_id"
        uplevel 1 "_${::aa_package_key}__c_$component_id"
        return
    } else {
        error "Unknown component $component_id, package $::aa_package_key"
    }
}

d_proc -public aa_register_case {
    {-libraries {}}
    {-cats {}}
    {-error_level "error"}
    {-bugs {}}
    {-procs {}}
    {-urls {}}
    {-init_classes {}}
    {-on_error {}}
    testcase_id
    testcase_desc
    args
} {
    Registers a testcase with the acs-automated-testing system.
    Whenever possible, cases that fail to register are replaced with
    'metatest' log cases, so that the register-time errors are visible
    at test time.

    See <a href="/doc/tutorial-debug">the tutorial</a> for examples.

    @param libraries A list of keywords of additional code modules to
    load.  The entire test case will fail if any package is missing.
    Currently includes <b>tclwebtest</b>.

    @param cats Properties of the test case.  Must be zero or more of the following:
    <ul>
    <li><b>db</b>: Tests the database directly
    <li><b>api</b>: tests the Tcl API
    <li><b>web</b>: tests HTTP interface
    <li><b>smoke</b>: Minimal test to assure functionality and catch basic errors.
    <li><b>stress</b>: Puts heavy load on server or creates large numbers of records. \
        Intended to simulate maximal production load.
    <li><b>security_risk</b>: May introduce a security risk.
    <li><b>populator</b>: Creates sample data for future use.
    <li><b>production_safe</b>: Can be used on a live production site, \
        i.e. for sanity checking or keepalive purposes. \
        Implies: no risk of adding or deleting data; no risk of crashing; minimal cpu/db/net load.
    </ul>

    @param error_level Force all test failures to this error level. One of
    <ul>
    <li><b>notice</b>: Informative.  Does not indicate an error.
    <li><b>warning</b>: May indicate an problem. \
        Example: a non-critical bug repro case that hasn't been fixed.
    <li><b>error</b>: normal error
    <li><b>metatest</b>: Indicates a problem with the test framework, execution, or reporting. \
        Suggests that current test results may be invalid. \
        Use this for test cases that test the tests. \
        Also used, automatically, for errors sourcing test cases.
    </ul>

    @param bugs A list of integers corresponding to openacs.org bug numbers which relate to this test case.
    @param procs A list of OpenACS procs which are tested by this case.
    @param urls A list of URLs (relative to package) tested in web test case

    @param on_error Deprecated.
    @param init_classes Deprecated.

    @author Peter Harper
    @creation-date 24 July 2001
} {
    # error reporting kludge: if there is any text in this variable
    # we'll not register this test case but indicate in the test case
    # body that there was an error.
    set case_error ""

    set allowed_error_levels { notice warning metatest error }
    if {$error_level ni $allowed_error_levels} {
        set error_level metatest
        append case_error "error_level must be one of following: $allowed_error_levels.\n\n"
    }

    set allowed_categories [nsv_get aa_test categories]
    foreach cat $cats {
        if {$cat ni $allowed_categories} {
            set error_level metatest
            append case_error "cats must contain only the following: $allowed_categories. You had a '$cat' in there.\n\n"
        }
    }

    #
    # Work out the package_key.
    #
    set package_root [file join $::acs::rootdir packages]
    set package_rel [string replace [info script] 0 [string length $package_root]]
    set package_key [lindex [file split $package_rel] 0]

    # run library specific code
    foreach library $libraries {
        if { $library eq "tclwebtest" } {

            # kludge: until tclwebtest installs itself in the proper
            # place following the Tcl way, we use this absolute path
            # hack.
            set tclwebtest_absolute_path "/usr/local/tclwebtest/lib"
            if { ![info exists ::auto_path] || $tclwebtest_absolute_path ni $::auto_path } {
                lappend ::auto_path $tclwebtest_absolute_path
            }
            if { [catch {
                package require tclwebtest
                package require http
            } err] } {
                set error_level metatest
                append case_error "tclwebtest is not available. Not registering this test case.\n\nError message: $err\n\n"
            }
        }
    }

    #
    # Print warnings for any unknown init_classes.  We actually mask out
    # any unknown init_classes here, so we don't get any script errors later.
    #
    set filtered_inits {}
    foreach init_class $init_classes {
        if {[llength $init_class] == 2} {
            set init_class [lindex $init_class 0]
        }
        if {[string trim $init_class] ne ""} {
            set found 0
            foreach init_class_info [nsv_get aa_test init_classes] {
                if {$init_class == [lindex $init_class_info 0]} {
                    set found 1
                }
            }
            if {!$found} {
                ns_log warning " aa_register_case: Unknown init class $init_class"
            } else {
                lappend filtered_inits $init_class
            }
        }
    }
    set init_classes $filtered_inits


    set test_case_list [list $testcase_id $testcase_desc \
                            [info script] $package_key \
                            $cats $init_classes $on_error $args $error_level \
                            $bugs $procs $urls]
    foreach p $procs {
        set p [string trimleft $p :]
        api_add_to_proc_doc -proc_name $p -property testcase -value [list $testcase_id $package_key]
        #ns_log notice "TESTCASE: api_add_to_proc_doc -proc_name $p -property testcase -value $testcase_id -> [dict get [nsv_get api_proc_doc $p] testcase]"
    }
    #
    # First, search the current list of test cases. If an old version already
    # exists, replace it with the new version.
    #
    set lpos 0
    set found_pos -1
    foreach case [nsv_get aa_test cases] {
        if {[lindex $case 0] == $testcase_id
            && [lindex $case 3] == $package_key
        } {
            nsv_set aa_test cases [lreplace [nsv_get aa_test cases] $lpos $lpos \
                                       $test_case_list]
            set found_pos $lpos
            break
        }
        incr lpos
    }
    #
    # If we haven't already replaced an existing entry, append the new
    # entry to the list.
    #
    if {$found_pos == -1} {
        nsv_lappend aa_test cases $test_case_list
    }

    if { $case_error ne "" } {

        # we don't source this file but insert a little warning text
        # into the procs body. There seems to be no better way to
        # indicate that this test should be skipped.

        d_proc -private _${package_key}__$testcase_id {} "
          # make sure errorlevel gets through. this is not 100% cleaned up.
          global error_level
          set error_level $error_level
          aa_log_result $error_level \{${case_error}\}"
        return
    }

    if {[llength $init_classes] == 0} {
        set init_class_code ""
    } else {
        set init_class_code [string map [
        list @init_classes@ [list $init_classes] @package_key@ [list $package_key]] {
            upvar 2 _aa_exports _aa_exports
            foreach init_class @init_classes@ {
                if {[llength $init_class] == 2} {
                    lassign $init_class init_class init_package_key
                } else {
                    set init_package_key @package_key@
                }
                foreach v $_aa_exports([list $init_package_key $init_class]) {
                    upvar 2 $v $v
                }
                foreach logpair $::aa_init_class_logs([list $init_package_key $init_class]) {
                    aa_log_result [lindex $logpair 0] [lindex $logpair 1]
                }
            }
        }]
    }

    set body [string map [list @init_class_code@ $init_class_code @args@ [list $args] @testcase_id@ [list $testcase_id]] {
        @init_class_code@
        set _aa_export {}
        set body_count 1
        foreach testcase_body @args@ {
          aa_log "Running testcase body $body_count"
          set ::__aa_test_indent [info level]
          set catch_val [catch $testcase_body msg]
          if {$catch_val != 0 && $catch_val != 2} {
              aa_log_result "fail" "@testcase_id@ (body $body_count): Error during execution: $msg, stack trace: \n$::errorInfo"
          }
          incr body_count
        }
    }]

    d_proc -private _${package_key}__$testcase_id {} $body
    ns_log Debug "aa_register_case: Registered test case $testcase_id in package $package_key"

}

d_proc -public aa_export_vars {
    varnames
} {
    Called from an initialization class constructor or a component to
    explicitly export the specified variables to the current testcase. You need
    to call aa_export_vars <b>before</b> you create the variables.

    Example:
    <pre>
    aa_export_vars {package_id item_id}
    set package_id 23
    set item_id 109
    </pre>
} {
    uplevel 1 [string map [list @varnames@ [list $varnames]] {
        foreach v @varnames@ {
          upvar $v $v
          uplevel 1 [list lappend _aa_export $v]
        }
    }]
}

d_proc -private aa_runseries {
    {-stress 0}
    {-security_risk 0}
    -quiet:boolean
    {-testcase_id ""}
    {by_package_keys ""}
    {by_category ""}
} {
    Runs a series of testcases.

    Runs all cases if both by_package_keys and by_category are blank,
    otherwise it uses the package and/or category to select which
    testcases to run.

    @author Peter Harper
    @creation-date 24 July 2001
} {
    # probably transitional code for testing purposes
    if {[info commands ::aa::coverage::add_traces] ne ""} {
        catch {aa::coverage::add_traces}
    }

    set ::aa_run_quietly_p $quiet_p
    #
    # Work out the list of initialization classes.
    #
    set testcase_ids {}
    if {$testcase_id ne ""} {
        lappend testcase_ids $testcase_id
        foreach testcase [nsv_get aa_test cases] {
            if {$testcase_id == [lindex $testcase 0]} {
                set package_key     [lindex $testcase 3]
                set init_classes    [lindex $testcase 5]
                foreach init_class $init_classes {
                    set classes([list $package_key $init_class]) 1
                }
            }
        }
    } else {
        foreach testcase [nsv_get aa_test cases] {
            set testcase_id     [lindex $testcase 0]
            set package_key     [lindex $testcase 3]
            set categories      [lindex $testcase 4]
            set init_classes    [lindex $testcase 5]

            # try to disqualify the test case

            # check if package key belongs to the ones we are testing
            if { $by_package_keys ne "" && $package_key ni $by_package_keys } {
                continue
            }

            # is it the wrong category?
            if { $by_category ne "" && $by_category ni $categories } {
                continue
            }

            # if we don't want stress, then the test must not be stress
            if { ! $stress && "stress" in $categories } {
                continue
            }

            # if we don't want security risks, then the test must not be stress
            if { ! $security_risk && "security_risk" in $categories } {
                continue
            }

            # we made it through the filters, so add the test case
            lappend testcase_ids $testcase_id
            foreach init_class $init_classes {
                set classes([list $package_key $init_class]) 1
            }
        }
    }
    #
    # Run each initialization script.  Keep a list of the exported variables
    # by each initialization script so each testcase (and destructor) can
    # correctly upvar to gain visibility of them.
    #
    if {[info exists classes]} {
        foreach initpair [array names classes] {
            lassign $initpair package_key init_class
            set _aa_export {}
            set ::aa_init_class_logs([list $package_key $init_class]) {}
            set ::aa_in_init_class [list $package_key $init_class]
            _${package_key}__i_$init_class
            set _aa_exports([list $package_key $init_class]) $_aa_export
        }
    }
    set ::aa_in_init_class ""

    #
    # Run each testcase
    #
    foreach testcase_id [lsort $testcase_ids] {
        set logStats [ns_logctl stats]
        ns_log notice "========================================= start $testcase_id" \
            "(Errors: [dict get $logStats Error], Warnings: [dict get $logStats Warning], Bugs: [dict get $logStats Bug])"
        aa_test_start
        aa_run_testcase $testcase_id
        aa_test_end
        set logStats [ns_logctl stats]
        ns_log notice "========================================= end $testcase_id" \
            "(Errors: [dict get $logStats Error], Warnings: [dict get $logStats Warning], Bugs: [dict get $logStats Bug])"
    }

    #
    # Run each initialization destructor script.
    #
    if {[info exists classes]} {
        foreach initpair [array names classes] {
            lassign $initpair package_key init_class
            set ::aa_in_init_class [list $package_key $init_class]
            _${package_key}__d_$init_class
        }
    }
    set ::aa_in_init_class ""

    # Generate the XML report file
    aa_test::write_test_file
}

ad_proc -private aa_indent {} {
    try to make it easier to read nested test cases.
} {
    if {[info exists ::__aa_test_indent]} {
        return "<tt>[string repeat {<span class='vl'></span>} [expr {[info level] - $::__aa_test_indent -2}]]</tt>"
    }
}

d_proc -private aa_run_testcase {
    testcase_id
} {
    @author Peter Harper
    @creation-date 24 July 2001
} {
    upvar  exports exports

    set ::aa_stub_names {}
    set ::aa_testcase_id $testcase_id
    set ::aa_testcase_test_id 0
    set ::aa_testcase_fails 0
    set ::aa_testcase_passes 0

    #
    # Lookup the testcase definition.
    #
    set testcase_bodys {}
    foreach testcase [nsv_get aa_test cases] {
        if {$testcase_id == [lindex $testcase 0]} {
            set testcase_file       [lindex $testcase 2]
            set package_key         [lindex $testcase 3]
            set testcase_cats       [lindex $testcase 4]
            set testcase_inits      [lindex $testcase 5]
            set testcase_on_error   [lindex $testcase 6]
            set testcase_bodys      [lindex $testcase 7]
            set ::aa_error_level    [lindex $testcase 8]
            set ::aa_package_key    $package_key
        }
    }
    if {[llength $testcase_bodys] == 0} {
        return
    }

    #
    # Create any file-wide stubs.
    #
    if {[nsv_exists aa_file_wide_stubs "$testcase_file"]} {
        foreach stub_def [nsv_get aa_file_wide_stubs "$testcase_file"] {
            aa_stub [lindex $stub_def 0] [lindex $stub_def 1]
        }
    }

    #
    # Run the test
    #
    db_dml delete_testcase_results {delete from aa_test_results where testcase_id = :testcase_id}
    db_dml delete_testcase_final_results {delete from aa_test_final_results where testcase_id = :testcase_id}

    ns_log debug "aa_run_testcase: Running testcase $testcase_id"

    set catch_val [catch _${package_key}__$testcase_id msg]
    if {$catch_val} {
        aa_log_result "fail" "$testcase_id: Error calling testcase function _${package_key}__$testcase_id: $msg"
    }

    aa_check_leftovers -silent end

    #
    # Unstub any stubbed functions
    #
    foreach stub_name $::aa_stub_names {
        aa_unstub $stub_name
    }
    set ::aa_stub_names {}

    aa_log_final $::aa_testcase_passes $::aa_testcase_fails
    unset ::aa_testcase_id

    #
    # Cleanup temporary XOTcl objects
    #
    if {[namespace which ::xo::at_cleanup] ne ""} {
        ::xo::at_cleanup
    }
}


d_proc -public aa_equals {
    affirm_name
    affirm_actual
    affirm_value
} {
    Tests that the affirm_actual is equal to affirm_value.<p>
    Call this function within a testcase, stub or component.

    @return True if the affirmation passed, false otherwise.

    @author Peter Harper
    @creation-date 24 July 2001
} {
    if {$affirm_actual eq $affirm_value} {
        aa_log_result "pass" [subst {[aa_indent$affirm_name, actual = "$affirm_actual"}]
        return 1
    } else {
        aa_log_result "fail" [subst {[aa_indent$affirm_name, actual = "$affirm_actual", expected = "$affirm_value"}]
        return 0
    }
}

d_proc -public aa_true {
    affirm_name
    affirm_expr
} {
    Tests that affirm_expr is true.<p>
    Call this function within a testcase, stub or component.

    @return True if the affirmation passed, false otherwise.

    @author Peter Harper
    @creation-date 24 July 2001
} {
    set result [uplevel 1 [list expr $affirm_expr]]
    if {$affirm_expr in {0 1 t f true false}} {
        set expr ""
    } else {
        set expr [subst {"$affirm_expr" }]
    }
    if { $result } {
        aa_log_result "pass" "[aa_indent$affirm_name: $expr true"
        return 1
    } else {
        aa_log_result "fail" "[aa_indent$affirm_name: $expr false"
        return 0
    }
}

d_proc -public aa_false {
    affirm_name
    affirm_expr
} {
    Tests that affirm_expr is false.
    Call this function within a testcase, stub or component.

    @return True if the affirmation passed, false otherwise.

    @author Peter Harper
    @creation-date 24 July 2001
} {
    set result [uplevel 1 [list expr $affirm_expr]]
    if {!$result} {
        aa_log_result "pass" [subst {[aa_indent$affirm_name: "$affirm_expr" false}]
        return 1
    } else {
        aa_log_result "fail" [subst {[aa_indent$affirm_name: "$affirm_expr" true}]
        return 0
    }
}

d_proc -public aa_section {
    log_notes
} {
    Writes a log message indicating a new section to the log files.
} {
    aa_log_result "sect" $log_notes
    ns_log notice "--------- aa_section" $log_notes
}

ad_proc -public aa_test_running_p {} {

    Check, if the regression test is currently running.

    @return boolean value indicating state
} {
    return [info exists ::__aa_testing_mode]
}

ad_proc -public aa_test_start {} {

    Set the start flag of the regression test case.
} {
    return [set ::__aa_testing_mode 1]
}

ad_proc -private aa_test_end {} {

    Clear the flag indicating that a regressoin test is running.  It
     is not always necessary to call this procedurfe explicitly, since
     the server cleanup clears this flag automatically.

} {
    unset -nocomplain ::__aa_testing_mode 1
}

ad_proc -public aa_log { args } {
    Writes a log message to the testcase log.
    Call this function within a testcase, stub or component.

    @author Peter Harper
    @creation-date 24 July 2001
} {
    set log_notes [join $args " "]
    #
    # When aa_run_quietly_p exists, we run inside the testing
    # environment.
    #
    if {[info exists ::aa_run_quietly_p]} {
        if {$::aa_run_quietly_p} {
            return
        }
        aa_log_result "log" "[aa_indent$log_notes"
    } else {
        #
        # Use plain ns_log reporting
        #
        ns_log notice "aa_log: $log_notes"
    }
}

d_proc -public aa_error {
    error_notes
} {
    Writes an error message to the testcase log.<p>
    Call this function within a testcase, stub or component.
    @author Peter Harper
    @creation-date 04 November 2001
} {
    aa_log_result "fail" $error_notes
}

d_proc -public aa_log_result {
    test_result
    args
} {
    Log a test result

    @author Peter Harper
    @creation-date 24 July 2001
} {
    set test_notes [join $args ""]
    if { [aa_in_rollback_block_p] } {
        aa_add_rollback_test [list aa_log_result $test_result $test_notes]
        return
    }

    #
    # When aa_run_quietly_p exists, we run inside the testing
    # environment. Otherwise, report and return.
    #
    if {![info exists ::aa_run_quietly_p]} {
        ns_log warning "aa_log_result: called outside the testing environment." \
            "Test result: $test_result Test notes: $test_notes"
            return
    }
    #
    # If logging is happened whilst in an initialization class, store the log
    # entry, but don't write it to the database.  Individual testcase will make
    # their own copies of these log entries.
    #
    if {$::aa_in_init_class ne ""} {
        lappend ::aa_init_class_logs($::aa_in_init_class) \
            [list $test_result $test_notes]
        return
    }

    incr ::aa_testcase_test_id
    if {$test_result eq "pass"} {
        ns_log Debug "aa_log_result: PASSED: $::aa_testcase_id$test_notes"
        incr ::aa_testcase_passes
    } elseif {$test_result eq "fail"} {
        switch $::aa_error_level {
            notice {
                ns_log notice "aa_log_result: NOTICE: $::aa_testcase_id$test_notes"
                set test_result "note"
            }
            warning {
                ns_log warning "aa_log_result: WARNING: $::aa_testcase_id$test_notes"
                set test_result "warn"
            }
            error {
                incr ::aa_testcase_fails
                ns_log Bug "aa_log_result: FAILED: $::aa_testcase_id$test_notes"
            }
            default {
                # metatest
                incr ::aa_testcase_fails
                ns_log Bug "aa_log_result: FAILED: Automated test did not function as expected:" \
                    "$::aa_testcase_id$test_notes"
            }
        }
    } elseif {$test_result ne "sect"} {
        ns_log Debug "aa_log_result: LOG: $::aa_testcase_id$test_notes"
        set test_result "log"
    }
    # Notes in database can only hold so many characters
    if { [string length $test_notes] > 2000 } {
        set test_notes "[string range $test_notes 0 1996]..."
    }

    global aa_package_key
    global aa_testcase_test_id
    global aa_testcase_id

    db_dml test_result_insert {
        insert into aa_test_results
        (testcase_id, package_key, test_id, timestamp, result, notes)
        values (:aa_testcase_id, :aa_package_key, :aa_testcase_test_id,
                current_timestamp, :test_result, :test_notes)
    }
}

d_proc -private aa_log_final {
    test_passes
    test_fails
} {
    @author Peter Harper
    @creation-date 24 July 2001
} {
    if {$test_fails > 0} {
        ns_log Bug "aa_log_final: FAILED: $::aa_testcase_id$test_fails tests failed"
    }

    global aa_testcase_id
    global aa_package_key

    db_dml testcase_result_insert {
        insert into aa_test_final_results
               (testcase_id, package_key, timestamp, passes, fails)
        values (:aa_testcase_id, :aa_package_key, current_timestamp, :test_passes, :test_fails)
    }
}

d_proc -public aa_run_with_teardown {
    {-test_code:required}
    {-teardown_code ""}
    -rollback:boolean
} {
    Execute code in test_code and guarantee that code in
    teardown_code will be executed even if error is thrown. Will catch
    errors in teardown_code as well and provide stack traces for both code blocks.

    @param test_code     Tcl code that sets up the test case and executes tests

    @param teardown_code Tcl code that tears down database data etc. that needs to execute
    after testing even if error is thrown.

    @param rollback      If specified, any db transactions in test_code will be rolled back.

    @author Peter Marklund
} {
    if { $rollback_p } {
        set test_code [string map [list @test_code@ $test_code] {
            set errmsg {}
            db_transaction {
               aa_start_rollback_block

               @test_code@

                aa_end_rollback_block
                error "rollback tests"
            } on_error {
                #
                # Execute the rollback block and trigger error.
                #
                aa_end_rollback_block
                set errmsg [lindex [split $::errorInfo \n] 0]
            }

            aa_execute_rollback_tests

            if { $errmsg ne {} && $errmsg ne "rollback tests" } {
                error "$errmsg \n\n $::errorInfo"
            }
        }]
    }

    # Testing
    set setup_error_p [catch {uplevel 1 $test_code} setup_error]
    set setup_error_stack $::errorInfo

    # Teardown
    set teardown_error_p 0
    if { $teardown_code ne "" } {
        set teardown_error_p [catch {uplevel 1 $teardown_code} teardown_error]
        set teardown_error_stack $::errorInfo
    }

    # Provide complete error message and stack trace
    set error_text ""
    if { $setup_error_p } {
        append error_text "Setup failed with error $setup_error\n\n$setup_error_stack"
    }
    if { $teardown_error_p } {
        append error_text "\n\nTeardown failed with error $teardown_error\n\n$teardown_error_stack"
    }
    if { $error_text ne "" } {
        error $error_text
    }
}

ad_proc -private aa_start_rollback_block {} {
    Start a block of code that is to be rolled back in the db

    @author Peter Marklund
} {
    global aa_in_rollback_block_p
    set aa_in_rollback_block_p 1
}

ad_proc -private aa_end_rollback_block {} {
    End a block of code that is to be rolled back in the db

    @author Peter Marklund
} {
    global aa_in_rollback_block_p
    set aa_in_rollback_block_p 0
}

ad_proc -private aa_in_rollback_block_p {} {
    Return 1 if we are in a block of code that is to be rolled back in the db
    and 0 otherwise.

    @author Peter Marklund
} {
    global aa_in_rollback_block_p
    if { [info exists aa_in_rollback_block_p] } {
        return $aa_in_rollback_block_p
    } else {
        return 0
    }
}

ad_proc -private aa_add_rollback_test {args} {
    Add a test statement that is to be executed after a rollback block.
    If it were to be executed during the rollback block it would be
    rolled back and this is what we want to avoid.

    @author Peter Marklund
} {
    global aa_rollback_test_statements

    lappend aa_rollback_test_statements $args
}

ad_proc -private aa_execute_rollback_tests {} {
    Execute all test statements from a rollback block.

    @author Peter Marklund
} {
    global aa_rollback_test_statements

    if { [info exists aa_rollback_test_statements] } {
        foreach test_statement $aa_rollback_test_statements {
            eval [join $test_statement " "]
        }
    }

    if { [info exists aa_rollback_test_statements] } {
        unset aa_rollback_test_statements
    }
}




namespace eval acs::test {

    d_proc -public ::acs::test::require_package_instance {
        -package_key:required
        {-instance_name ""}
        {-empty:boolean}
    } {
        Returns a test instance of specified package_key mounted under
        specified name. Will create it if it is not found. It is
        currently assumed the instance will be mounted under the main
        subsite.

        @param package_key package to be instantiated
        @param instance_name name of the site-node this instance will
               be mounted to. Will default to <package_key>-test
        @param empty require an empty instance. If an existing
               instance is found, it will be deleted. If a package
               different than <package_key> is found, it won't be
               deleted and the proc will return an error

        @return a package_id
    } {
        set main_node_id [site_node::get_element \
                              -url / -element node_id]

        set instance_name [expr {$instance_name eq "" ?
                                 "${package_key}-test" : [string trim $instance_name /]}]

        set package_exists_p [db_0or1row lookup_test_package {
            select node_id, object_id as package_id
            from site_nodes
            where parent_id = :main_node_id
            and name = :instance_name
            and object_id is not null
        }]

        if {$package_exists_p} {
            set existing_package_key [apm_package_key_from_id $package_id]
            if {$existing_package_key ne $package_key} {
                error "An instance of '$existing_package_key' is already mounted at '$instance_name'"
            } elseif {$empty_p} {
                site_node::delete -node_id $node_id -delete_package
            }
        }

        if {!$package_exists_p || $empty_p} {
            set package_id [site_node::instantiate_and_mount \
                                -package_name $instance_name \
                                -node_name $instance_name \
                                -package_key $package_key]
        }

        return $package_id
    }

    d_proc -public ::acs::test::form_reply {
        {-user_id 0}
        {-last_request ""}
        {-form ""}
        {-url ""}
        {-update {}}
        {-remove {}}
        {form_content ""}
    } {

        Send a (POST) request to the specified URL based on the
        provided form_content which has the form of a dict.  For
        convenience the update fields are provided to overload the
        form_content.

        @param last_request pass optionally the past request, from which cookie and login-info can be taken
        @param update key/attribute list of values to be updated in the form content
        @param remove keys to be removed from the form content

    } {
        if {$form_content eq ""} {
            set form_content [form_get_fields $form]
            aa_log "FORM-CONTENT from FORM '$form_content'"
        }
        if {$form_content eq ""} {
            error "either nonempty form or form_content has to be provided"
        }
        if {$url eq ""} {
            set url [dict get $form @action]
        }
        if {$url eq ""} {
            error "either form with action fields or URL has to be provided"
        }

        if {$remove ne ""} {
            set form_content [dict remove $form_content {*}$remove]
            ns_log notice "DEBUG: after removing <$remove> from <$form_content>"
        }

        #
        # Update the values coming from the form with our values.
        #
        foreach {att value} $update {
            if {[regexp {^(.*)\.(tmpfile|content-type)$} $att _ fieldname type]} {
                #
                # This parameter is the attribute of a file.
                #
                lappend files($fieldname$type $value
            } else {
                #
                # This is a normal parameter
                #
                dict set form_content $att $value
            }
        }

        #
        # Cleanup all form parameters that will be sent as files
        #
        set form_content [dict remove $form_content {*}[array names files]]

        #
        # Now take all of the parameters that are files and build up
        # the list to pass to the payload creation.
        #
        set fs {}
        foreach {fieldname attrs} [array get files] {
            if {![dict exists $attrs tmpfile]} {
                error "'$fieldname' looks like a file upload, but no .tmpfile was specified"
            }
            set f [list \
                       fieldname $fieldname \
                       file [dict get $attrs tmpfile]]
            if {[dict exists $attrs content_type]} {
                lappend f mime_type [dict get $attrs content_type]
            }
            lappend fs $f
        }

        set payload [util::http::post_payload \
                         -files $fs \
                         -formvars_list $form_content]
        #
        # Send the POST request
        #
        return [http \
                    -user_id $user_id \
                    -last_request $last_request \
                    -method POST \
                    -body [dict get $payload payload] \
                    -headers [ns_set array [dict get $payload headers]] \
                    $url]
    }

    ad_proc -public ::acs::test::url {} {
        @return the test URL representing our system for testing. This
        would normally look like the output of ns_conn location,
        unless it was overridden via the TestURL parameter in this
        package.
    } {
        #
        # Check, if a testURL was specified in the config file
        #
        # ns_section ns/server/${server}/acs/acs-automated-testing
        #         ns_param TestURL http://127.0.0.1:8080/
        #
        set url [parameter::get \
                     -package_id [apm_package_id_from_key acs-automated-testing] \
                     -parameter TestURL \
                     -default ""]
        if {$url eq ""} {
            set url [ns_conn location]
        }

        return $url
    }

    d_proc -public ::acs::test::http {
        {-user_id 0}
        {-user_info ""}
        {-last_request ""}
        {-method GET}
        {-body}
        {-timeout 10}
        {-depth 1}
        {-headers ""}
        {-prefix ""}
        {-verbose:boolean true}
        {-basic_auth:boolean false}
        request
    } {

        Run an HTTP request against the actual server inside test
        cases.

        @param depth follow redirects up to specified depth. Default
        means redirects won't be followed.

        @author Gustaf Neumann
    } {
        ns_log notice "::acs::test::http -user_id '$user_id' -user_info '$user_info' request '$request'"
        set session ""
        if {[dict exists $last_request session]} {
            set session [dict get $last_request session]
        }
        if {$user_info eq "" && [dict exists $session user_info]} {
            set user_info [dict get $last_request session user_info]
            #aa_log "user_info from last_request [ns_quotehtml <$user_info>]"
        }
        #aa_log "HTTP: user_info [ns_quotehtml <$user_info>]"
        #aa_log "HTTP: start session_info [ns_quotehtml <$session>]"

        set test_url [acs::test::url]
        set urlInfo [ns_parseurl $test_url]
        set address [dict get $urlInfo host]
        set url ${test_url}/${request}

        #
        # Either authenticate via user_info (when specified) or via
        # user_id.
        #
        if {$user_info ne ""} {
        } else {
            dict set user_info user_id $user_id
            dict set user_info address $address
        }

        set session [::acs::test::set_user -session $session $user_info]
    ns_log notice "Session after set_user '$user_info': $session"
        set login [dict get $session login]

        if {[dict exists $session cookies]} {
            lappend headers Cookie [dict get $session cookies]
        }

        set extra_args {}
        if {[info exists body]} {
            lappend extra_args -body $body
        }

        if {[dict exists $user_info email]
            && [dict exists $user_info password]
        } {
            set ah [ns_base64encode [dict get $user_info email]:[dict get $user_info password]]
            aa_log "... user_info $user_info AH $ah"
            lappend headers Authorization "Basic $ah"
        }

        if {[llength $headers] > 0} {
            set requestHeaders [ns_set create]
            foreach {tag value} $headers {
                ns_set update $requestHeaders $tag $value
            }
            lappend extra_args -headers $requestHeaders
        }

        #
        # Construct a nice log line
        #
        append log_line "${prefix}Run $method $request"
        if {[llength $headers] > 0} {
            append log_line " (headers: $headers)"
        }
        if {[info exists body]} {
            append log_line "<pre>\n[ns_quotehtml $body]</pre>"
        }
        aa_log $log_line

        #
        # Run actual request
        #
        set d ""
        try {
            set location $url
            while {$depth > 0} {
                ns_log notice "acs::test::http client request (timeout $timeout): $method $location"
                incr depth -1
                set d [ns_http run \
                           -timeout $timeout \
                           -method $method \
                           {*}$extra_args \
                           $location]
                set status   [dict get $d status]
                set location [ns_set iget [dict get $d headers] location]
                if {![string match "3??" $status] || $location eq ""} {
                    break
                }

                #
                # According to
                # https://www.rfc-editor.org/rfc/rfc7231#section-7.1.2,
                # the location header may return a relative URL as
                # well.
                #
                set location [ns_absoluteurl $location $test_url]
            }
        } finally {
            #
            # always reset after the request the login data nsv
            #
            nsv_unset -nocomplain aa_test logindata
        }

        #ns_log notice "run $request returns $d"
        #ns_log notice "... [ns_set array [dict get $d headers]]"

        if {$verbose_p} {
            set ms [format %.2f [expr {[ns_time format [dict get $d time]] * 1000.0}]]
            aa_log "${prefix}$method $request returns [dict get $d status] in ${ms}ms"
        }

        #aa_log "REPLY has headers [dict exists $d headers]"
        if {[dict exists $d headers]} {
            set cookies {}
            set cookie_dict {}
            if {[dict exists $last_request cookies]} {
                #
                # Merge last request cookies
                #
                foreach cookie [split [dict get $last_request cookies] ";"] {
                    lassign [split [string trim $cookie] =] name value
                    dict set cookie_dict $name $value
                    #aa_log "merge last request cookie $name $value"
                }
            } else {
                #aa_log "last_req has no cookies"
            }
            if {[dict exists $session cookies]} {
                #
                # Merge session cookies (e.g. from a called login
                # inside :acs::test::set_user)
                #
                foreach cookie [split [dict get $session cookies] ";"] {
                    lassign [split [string trim $cookie] =] name value
                    dict set cookie_dict $name $value
                    #aa_log "merge session cookie $name $value"
                }
            }
            #
            # Merge fresh cookies
            #
            foreach {tag value} [ns_set array [dict get $d headers]] {
                #aa_log "received header $tag: $value"
                if {$tag eq "set-cookie"} {
                    if {[regexp {^([^;]+);} $value . cookie]} {
                        lassign [split [string trim $cookie] =] name value
                        dict set cookie_dict $name $value
                        aa_log "merge fresh cookie $name $value"
                    } else {
                        aa_log "Cookie has invalid syntax: $value"
                    }
                }
            }
            foreach cookie_name [dict keys $cookie_dict] {
                lappend cookies $cookie_name=[dict get $cookie_dict $cookie_name]
            }
            dict set d session cookies [join $cookies ";"]
        }
        dict set d login $login
        dict set d session user_info $user_info
        #aa_log "HTTP: URL $url final session_info [ns_quotehtml <[dict get $d session]>]"

        return $d
    }

    d_proc -private ::acs::test::set_user {
        {-session ""}
        user_info
    } {

        When (login) cookies are given as member of "session", use
        these. In case the login cookie is empty (after an explicit
        logout) do NOT automatically log in.

        When (login) cookies are not given, use "user_info" for
        authentication. When we have a "user_id" and "address" in the
        "user_info", use these for direct logins. Otherwise the person
        info (name, email, ...) to log via register.

        @param session when given, use login information from there
        @param user_info dict containing user_id+session and/or
               email, last_name, username and password
    } {
        #aa_log "set_user has user_info $user_info, have cookies: [dict exists $session cookies]"

        set already_logged_in 0
        #
        # First check, if the user is already logged in via cookies
        #
        if {[dict exists $session cookies]} {
            #aa_log "session has cookies '[dict get $session cookies]'"
            foreach cookie [split [dict get $session cookies] ";"] {
                lassign [split [string trim $cookie] =] name value
                #aa_log "session has cookie $cookie // NAME '$name' VALUE '$value'"
                if {$name in {ad_user_login ad_user_login_secure} && $value ne "\"\""} {
                    aa_log "user is already logged in via cookie $name"
                    set already_logged_in 1
                    dict set session login via_cookie
                    break
                }
            }
        }

        #aa_log "already_logged_in $already_logged_in"
        if {!$already_logged_in} {
            #
            # The user is not logged in via cookies, check first
            # available user_id. If this does not exist, perform login
            #
            #aa_log "not logged in, check $user_info"

            if {[dict exists $user_info user_id]
                && [dict exists $user_info address]
            } {
                set user_id [dict get $user_info user_id]
                if {$user_id ne 0} {
                    #aa_log "::acs::test::set_user set logindata via nsv"
                    set address [dict get $user_info address]
                    ad_try {
                        set peeraddr [ns_addrbyhost $address]
                    } on error {errorMsg} {
                        set peeraddr $address
                    }
                    set address $peeraddr
                    nsv_set aa_test logindata \
                        [list \
                             peeraddr $address \
                             user_id $user_id]
                    dict set session login via_logindata
                } else {
                    dict set session login none
                }
            } elseif {[dict exists $session cookies]} {
                #
                # We have cookies, but are not logged in. Do NOT automatically log in.
                #
                dict set session login none
            } else {
                #
                # No cookies, log automatically in.
                #
                #aa_log "::acs::test::set_user perform login with $user_info"
                set d [::acs::test::login $user_info]
                #aa_log "::acs::test::set_user perform login returned session [dict get $d session]"
                dict set session cookies [dict get $d session cookies]
                dict set session login via_login
            }
        }
        return $session
    }


    d_proc -public ::acs::test::login {
        user_info
    } {
        Login (register operation) in a web session

        @param user_info dict containing at least
               email, last_name, username and password
    } {
        #aa_log "acs::test::login with user_info $user_info"
        set d [acs::test::http -user_id 0 /register/]
        acs::test::reply_has_status_code $d 200

        set form [acs::test::get_form [dict get $d body ] {//form[@id='login']}]
        set fields [acs::test::form_get_fields $form]
        if {[dict exists $fields email]} {
            aa_log "login via email [dict get $user_info email]"
            dict set fields email [dict get $user_info email]
        } else {
            aa_log "login via username [dict get $user_info username]"
            dict set fields username [dict get $user_info username]
        }
        dict set fields password [dict get $user_info password]
        set form [acs::test::form_set_fields $form $fields]

        set d [::acs::test::form_reply -user_id 0 -form $form]
    ns_log notice "::acs::test::form_reply $form -->\n$d"
        acs::test::reply_has_status_code $d 302
        aa_test_start

        return $d
    }

    d_proc -public ::acs::test::logout {
        -last_request:required
    } {
        Logout from the current web session

        @param last_request reply dict containing cookies
    } {
        set d [acs::test::http -last_request $last_request /register/logout]
        acs::test::reply_has_status_code $d 302
        aa_test_end

        return $d
    }


    d_proc -public ::acs::test::get_url_from_location {
        dict
    } {
        Determine the URL based on the location field provided from
        the result dict (as returned from acs::test::http).

        @param dict dict containing an ns_set called headers
        @see acs::test::http
    } {
        set location [ns_set iget [dict get $dict headers] Location ""]
        if {$location ne ""} {
            set urlDict [ns_parseurl $location]
            #aa_log "parse URL '$location' => $urlDict"
            if {[dict get $urlDict tail] ne ""} {
                set url [dict get $urlDict path]/[dict get $urlDict tail]
            } else {
                set url [dict get $urlDict path]/
            }
            if {[dict exists $urlDict query]} {
                set query [dict get $urlDict query]
                if {$query ne ""} {
                    append url "?$query"
                }
            }
        } else {
            set url ""
        }
        return $url
    }

    d_proc -public ::acs::test::confirm_email {
        -user_id:required
    } {
        Confirms user email
    } {
        # Call the confirmation URL and check response
        set token [auth::get_user_secret_token -user_id $user_id]
        set to_addr [party::get -party_id $user_id -element email]
        set confirmation_url [export_vars -base "/register/email-confirm" { token user_id }]
        set d [acs::test::http $confirmation_url]
        acs::test::reply_has_status_code $d 200
    }

    ad_proc -public ::acs::test::visualize_control_chars {lines} {
        Quotes and therefore makes visible control chars in input lines
    } {
        return [string map {\\ \\\\ \r \\r \n "\\n\n"$lines]
    }

    ad_proc -public ::acs::test::dom_html {var html body} {
        Parses HTML into a tDOM object and executes some code.

        @param var the variable name that body can refer to as
                   documentElement of the document (e.g. "root").
        @param html the markup to be parsed.
        @param body a Tcl script executed in the caller scope that can
                    assume the document to be parsed and be available
                    in "var".
    } {
        upvar $var root
        try {
            dom parse -html -- $html doc
        } on error {errorMsg} {
            ns_log error "Failed to parse the following HTML text with message: $errorMsg\n$html"
        }
        $doc documentElement root
        uplevel 1 $body
    }

    ad_proc -public get_form {body xpath} {

        Locate the HTML forms matching the XPath expression and
        retrieve its HTML attributes and the formfields in form of a
        Tcl dict. This is a convenience function, combining
        acs::test::dom_html and ::acs::test::xpath::get_form.

        @return Tcl dict with form attributes (starting with "@" and fields)
        @see acs::test::dom_html ::acs::test::xpath::get_form

        @author Gustaf Neumann
    } {
        acs::test::dom_html root $body {
            set form_data [::acs::test::xpath::get_form $root $xpath]
        }
        return $form_data
    }

    ad_proc -public form_get_fields {form} {

        Get the fields from a form.

        @form form dict
        @see acs::test::get_form

        @author Gustaf Neumann
    } {
        return [dict get $form fields]
    }

    ad_proc -public form_set_fields {form fields} {

        Set the fields in a form.

        @form form dict
        @fields fields in form of attribute/value pairs

        @see acs::test::get_form

        @author Gustaf Neumann
    } {
        dict set form fields $fields
        return $form
    }

    ad_proc -public form_is_empty {form} {

        Check, if the form is empty

        @form form dict

        @see acs::test::get_form

        @author Gustaf Neumann
    } {
        return [expr {[llength $form] == 0}]
    }


    d_proc -public follow_link {
        -last_request:required
        {-user_id 0}
        {-base /}
        {-label ""}
    } {

        Follow the first provided label and return the page info.
        Probably, we want as well other mechanisms to locate the
        anchor element later.

        @author Gustaf Neumann
    } {
        set href [find_link \
                      -last_request $last_request \
                      -user_id $user_id \
                      -base $base \
                      -label $label]
        return [http -last_request $last_request -user_id $user_id $href]
    }

    d_proc -public find_link {
        -last_request:required
        {-user_id 0}
        {-base /}
        {-label ""}
    } {

        Find the first link based on the provided label and return the href.

        @author Gustaf Neumann
    } {
        set href ""
        set html [dict get $last_request body]
        acs::test::dom_html root $html {
            foreach a [$root selectNodes //a] {
                set link_label [string trim [$a text]]
                if {$label eq $link_label} {
                    set href [$a getAttribute href]
                    break
                }
                #
                # There is something weird in tDOM: without the
                # "string trim" we see something like
                #
                #       a TEXT 'DD25C9878' = 'DD25C9878' eq 0 77 9
                #
                # from the statements below.
                # set eq [expr {$label eq $link_label}]
                # aa_log "a TEXT '$link_label' = '$label' eq $eq [string length $link_label] [string length $label]"
                # aa_log "a TEXT '[$a asHTML]'"
            }
        }
        aa_true "href '$href' of link with label '$label' is not empty (<a href='[detail_link $last_request]'>Details</a>)" \
            {$href ne ""}
        if {![string match "/*" $href]} {
            set href $base/$href
        }
        return $href
    }

    ad_proc -private detail_link {dict} {

        Create a detail link, which is useful for web-requests, to
        inspect the result in case a test fails.

        Missing: cleanup, e.g. after a couple of days, or when the
        testcase is executed again (for that we would need testcase_id
        and package_key, that we do not want to pass around)

    } {
        set nonce REPLY-[clock clicks -microseconds].html
        set F [open $::acs::rootdir/packages/acs-automated-testing/www/$nonce w]
        puts $F [dict get $dict body]
        close $F
        return /test/$nonce
    }

    ad_proc -public reply_contains {{-prefix ""} dict string} {

        Convenience function for test cases to check, whether the
        resulting page contains the given string.

        @param prefix  prefix for logging
        @param dict    request reply dict, containing at least the request body
        @param string  string to be checked on the page
    } {
        set result [string match *$string* [dict get $dict body]]
        if {$result} {
            aa_true "${prefix}Reply contains $string" $result
        } else {
            aa_true "${prefix}Reply contains $string (<a href='[detail_link $dict]'>Details</a>)" $result
        }
        return $result
    }

    ad_proc -public reply_contains_no {{-prefix ""} dict string} {

        Convenience function for test cases to check, whether the
        resulting page does not contain the given string.

        @param prefix  prefix for logging
        @param dict    request reply dict, containing at least the request body
        @param string  string to be checked on the page
    } {
        set result [string match *$string* [dict get $dict body]]
        if {$result} {
            aa_false "${prefix}Reply contains no $string (<a href='[detail_link $dict]'>Details</a>)" $result
        } else {
            aa_false "${prefix}Reply contains no $string" $result
        }
        return [expr {!$result}]
    }

    ad_proc -public reply_has_status_code {{-prefix ""} dict status_code} {

        Convenience function for test cases to check, whether the
        reply has the given status code.

        @param prefix       prefix for logging
        @param dict         request reply dict, containing at least the request status
        @param status_code  expected HTTP status codes

    } {
        set result [expr {[dict get $dict status] == $status_code}]
        if {$result} {
            aa_true "${prefix}Reply has status code $status_code" $result
        } else {
            aa_true "${prefix}Reply expected status code $status_code but got [dict get $dict status] (<a href='[detail_link $dict]'>Details</a>)" $result
        }
        return $result
    }

}

namespace eval ::acs::test::xpath {

    #
    # All procs in this namespace have the signature
    #   root xpath
    # where "root" is a DOM-node and "xpath" is an XPath expression.
    #
    ad_proc -public get_text {root xpath} {
        Get a text element from tdom via XPath expression.
        If the XPath expression matches multiple nodes,
        return a list.
    } {
        set nodes [$root selectNodes $xpath]
        switch [llength $nodes] {
            0 {set result ""}
            1 {set result [$nodes asText]}
            default {
                set result ""
                foreach n $nodes {
                    lappend result [$n asText]
                }
            }
        }
        return $result
    }


    ad_proc -public non_empty {node selectors} {

        Test if provided selectors return nonempty results

    } {
        #
        # if we have no node, use as default the root in the parent
        # environment
        #
        if {$node eq ""} {
            set node [uplevel 1 {set root}]
        }
        foreach q $selectors {
            try {
                set value [get_text $node $q]
            } on error {errorMsg} {
                aa_true "XPAth exception during evaluation of selector '$q': $errorMsg" 0
                throw {XPATH {xpath triggered exception}} $errorMsg
            }
            aa_true "XPath $q <$value>:" {$value ne ""}
        }
    }

    ad_proc -public equals {node pairs} {

        Test whether provided selectors (first element of the pair)
        return the specified results (second element of the pair).

    } {
        foreach {q value} $pairs {
            try {
                set result [get_text $node $q]
            } on error {errorMsg} {
                aa_true "XPAth exception during evaluation of selector '$q': $errorMsg" 0
                throw {XPATH {xpath triggered exception}} $errorMsg
            }

            aa_equals "XPath $q:" $result $value
        }
    }

    ad_proc -public get_form {node xpath} {

        Locate the HTML forms matching the XPath expression and
        retrieve its HTML attributes and the formfields in form of a
        Tcl dict.

        @return Tcl dict with form attributes (keys starting with "@", and entry "fields")

        @author Gustaf Neumann
    } {
        set d {}
        set formNodes [$node selectNodes $xpath]
        if {[llength $formNodes] > 1} {
            error "XPath expression must point to at most one HTML form"
        } else {
            #aa_log "xpath::get_form has form nodes '$formNodes'"
            foreach form $formNodes {
                foreach att [$node selectNodes $xpath/@*] {
                    #aa_log "xpath::get_form form '$form' has attribute '$att'"
                    dict set d @[lindex $att 0] [lindex $att 1]
                }
                dict set d fields [::acs::test::xpath::get_form_values $node $xpath]
            }
        }
        return $d
    }

    ad_proc -private get_name_attribute {node xpath} {
        if {![$node hasAttribute name]} {
            aa_log_result warning "input field $xpath has no 'name' attribute (ignored): " \
                "<pre>[ns_quotehtml [$node asHTML]]</pre>"
            return ""
        }
        return [$node getAttribute name]
    }

    ad_proc -public get_form_values {node xpath} {

        Obtain form values (input fields and textareas) in form of a
        dict (attribute value pairs). The provided XPath expression
        must point to the HTML form containing the values to be
        extracted.

    } {
        set values {}
        foreach n [$node selectNodes $xpath//input] {
            set name [get_name_attribute $n $xpath//input]
            if {$name eq ""} continue

            # Disabled attributes are not sent together with the form
            # on submit, so we do not fetch them.
            if {[$n hasAttribute disabled]} {
                continue
            }

            # Do not consider unchecked radio buttons or checkboxes as
            # values
            if {[$n getAttribute type ""] in {"radio" "checkbox"} &&
                ![$n hasAttribute checked]} {
                continue
            }

            #ns_log notice "aa_xpath::get_form_values from $className input node $n name $name:"
            if {[$n hasAttribute value]} {
                set value [$n getAttribute value]
            } else {
                set value ""
            }
            lappend values $name $value
        }
        foreach n [$node selectNodes $xpath//textarea] {
            set name [get_name_attribute $n $xpath//textarea]
            if {$name eq ""} continue

            # Disabled attributes are not sent together with the form
            # on submit, so we do not fetch them.
            if {[$n hasAttribute disabled]} {
                continue
            }

            #ns_log notice "aa_xpath::get_form_values from $className textarea node $n name $name:"
            set value [$n text]
            lappend values $name $value
        }
        foreach n [$node selectNodes $xpath//select/option\[@selected='selected'\]] {
            set name [get_name_attribute [$n parentNode] $xpath//option/..]
            if {$name eq ""} continue

            # Disabled attributes are not sent together with the form
            # on submit, so we do not fetch them.
            if {[$n hasAttribute disabled]} {
                continue
            }

            set value [$n getAttribute value]
            lappend values $name $value
        }

        return $values
    }
}

namespace eval acs::test::user {

    d_proc ::acs::test::user::create {
        {-admin:boolean}
        {-email ""}
        {-locale en_US}
        {-password ""}
        {-user_id ""}
    } {
        Create a test user with random email and password for testing.
        If an email is passed in and the party identified by the
        password exists, the user_id of this party is returned in the
        dict.

        @param user_id  user_id for the user to be created
        @param email    email for the user to be created
        @param password password for the user to be created
        @param admin    provide this switch to make the user site-wide admin
        @param locale   locale for the user to be created

        @return The user_info dict returned by auth::create_user. Contains
                the additional keys email and password.
    } {
        #
        # Currently, we are not able to reuse the testing account
        # based on email, since a later login attempt for that account
        # fails, since we have no cookie yet, and the testing
        # authority does not allow logins via /login.
        #
        if {$email ne "" && 0} {
            set party_info [party::get -email $email]
            if {[llength $party_info] > 0} {
                #
                # We have such a party already. Return the usual
                # elements like on new creation.
                #
                set d [acs_user::get -user_id [dict get $party_info party_id]]
                dict set user_info user_id [dict get $party_info party_id]
                dict set user_info password [dict get $d password]
                dict set user_info email [dict get $d email]
                dict set user_info first_names [dict get $d first_names]
                dict set user_info last_name [dict get $d last_name]
                return $user_info
            }
        }
        if {$password eq ""} {
            set password    [ad_generate_random_string]
        }
        set username "__test_user_[ad_generate_random_string]"
        set email "$username@test.test"

        set first_names [ad_generate_random_string]
        set last_name   [ad_generate_random_string]

        set user_info [auth::create_user \
                           -user_id $user_id \
                           -username $username \
                           -email $email \
                           -first_names $first_names \
                           -last_name $last_name \
                           -password $password \
                           -secret_question [ad_generate_random_string] \
                           -secret_answer [ad_generate_random_string] \
                           -authority_id [auth::authority::get_id -short_name "acs_testing"]]
        if {![dict exists $user_info user_id]} {
            aa_error "invalid USER_INFO (does not contain user_id): $user_info"
        }
        lang::user::set_locale -user_id [dict get $user_info user_id] $locale
        if { [dict get $user_info creation_status] ne "ok" } {
            # Could not create user
            error "Could not create test user with username=$username user_info=[array get user_info]"
        }

        dict set user_info password $password
        dict set user_info email $email
        dict set user_info first_names $first_names
        dict set user_info last_name $last_name

        #aa_log "Created user with email='$email' and password='$password'"
        aa_log "Created user with email='$email'"

        if { $admin_p } {
            aa_log "Making user site-wide admin"
            permission::grant -object_id \
                [acs_magic_object "security_context_root"] \
                -party_id [dict get $user_info user_id] \
                -privilege "admin"
        }

        return $user_info
    }

    d_proc ::acs::test::user::delete {
        {-user_id:required}
        {-delete_created_acs_objects:boolean false}
    } {
        Remove a test user.
    } {
        #
        # Delete modifying user info, since otherwise we cannot delete
        # the user_id. The modifying user is e.g. propagated to parent
        # objss when modifying a page in the content reposistory.
        #
        db_dml unset_modifying_user {
            UPDATE acs_objects
            SET modifying_user = NULL
            where modifying_user = :user_id
        }
        #
        # If desired, delete the created acs_objects of this user.
        #
        if {$delete_created_acs_objects_p} {
            db_dml unset_modifying_user {
                delete from acs_objects where creation_user = :user_id
            }
        }
        acs_user::delete \
            -user_id $user_id \
            -permanent
    }
}



namespace eval aa_test {}

ad_proc -public aa_test::xml_report_dir {} {
    Retrieves the XMLReportDir parameter.

    @return Returns the value for the XMLReportDir parameter.
} {
    return [parameter::get -parameter XMLReportDir]
}

d_proc -private aa_test::test_file_path {
    {-install_file_path:required}
} {
    set filename [file tail $install_file_path]
    regexp {^(.+)-(.+)-(.+)\.xml$} $filename match hostname server
    set test_path [file dirname $install_file_path]/${hostname}-${server}-testreport.xml

    return $test_path
}

d_proc -public aa_test::parse_install_file {
    {-path:required}
    {-array:required}
} {
    Processes the xml report outputted from install.sh for display.
} {
    upvar 1 $array service

    set tree [xml_parse -persist [template::util::read_file $path]]

    set root_node [xml_doc_get_first_node $tree]

    foreach entry {
        name os dbtype dbversion webserver openacs_cvs_flag adminemail adminpassword
        install_begin_epoch install_end_epoch install_end_timestamp num_errors
        install_duration install_duration_pretty script_path description
    } {
        set service($entry"n/a"
    }
    set service(path) $path
    set service(filename) [file tail $path]
    set service(parse_errors) {}

    set service(name) [xml_node_get_attribute $root_node "name"]
    if { $service(name) eq "" } {
        append service(parse_error) "No service name attribute;"
    }

    foreach child [xml_node_get_children $root_node] {
        set info_type [xml_node_get_attribute $child "type"]
        if { $info_type eq "" } {
            append service(parse_error) "No type on info tag;"
            continue
        }
        set info_type [string map {- _} $info_type]
        set info_value [xml_node_get_content $child]
        set service($info_type$info_value
    }

    if { [string is integer -strict $service(install_begin_epoch)] && [string is integer -strict $service(install_end_epoch)] } {
        set service(install_duration) [expr {$service(install_end_epoch) - $service(install_begin_epoch)}]
        set service(install_duration_pretty) [util::interval_pretty -seconds $service(install_duration)]
    }

    # TODO: Not working
    set service(admin_login_url) [export_vars -base $service(url)register/ {
        { email $service(adminemail) }
        { password $service(adminpassword) }
    }]
    set service(auto_test_url) "$service(url)test/admin"
    set service(rebuild_cmd) "sh [file join $service(script_path) recreate.sh]"
}

ad_proc -private aa_test::get_test_doc {} {
    Returns an XML doc with statistics for the most recent test results
    on the server.

    @author Peter Marklund
} {
    # Open XML document
    set xml_doc "<?xml version=\"1.0\"?>
    <test_report>\n"

    set testcase_count [llength [nsv_get aa_test cases]]
    append xml_doc "    <testcase_count>$testcase_count</testcase_count>\n"

    db_foreach result_counts {
        select result,
        count(*) as result_count
        from aa_test_results
        group by result
    } {
        set result_counts($result$result_count
    }

    foreach result [array names result_counts] {
        append xml_doc "    <result_count result=\"$result\">$result_counts($result)</result_count>\n"
    }

    db_foreach failure_counts {
        select testcase_id,
        count(*) as failure_count
        from aa_test_results
        where result = 'fail'
        group by testcase_id
    } {
        set failure_counts($testcase_id$failure_count
    }

    foreach testcase_id [array names failure_counts] {
        append xml_doc "    <testcase_failure testcase_id=\"$testcase_id\">$failure_counts($testcase_id)</testcase_failure>\n"
    }

    # Close XML document
    append xml_doc "</test_report>\n"

    return $xml_doc
}

ad_proc -private aa_test::write_test_file {} {
    Writes an XML file with statistics for the most recent test results
    on the server.

    @author Peter Marklund

} {
    set xml_doc ""

    set report_dir [aa_test::xml_report_dir]
    if { [file isdirectory $report_dir] } {

        set hostname [exec [::util::which hostname]]
        set server [ns_info server]
        set file_path "$report_dir/${hostname}-${server}-testreport.xml"

        set xml_doc [get_test_doc]

        if { [catch {template::util::write_file $file_path $xml_doc} errmsg] } {
            ns_log Error "Failed to write xml test report to path $file_path - $errmsg"
        }
    }

    return $xml_doc
}

d_proc -public aa_test::parse_test_file {
    {-path:required}
    {-array:required}
} {
    Processes the xml report with test result data for display.
} {
    upvar 1 $array test

    set tree [xml_parse -persist [template::util::read_file $path]]

    set root_node [xml_doc_get_first_node $tree]

    # Get the total test case count
    set testcase_count_node [xml_node_get_children_by_name $root_node testcase_count]
    set test(testcase_count) [xml_node_get_content $testcase_count_node]

    # Get the result counts by result type
    foreach result_count_node [xml_node_get_children_by_name $root_node result_count] {
        set result [xml_node_get_attribute $result_count_node result]
        set count [xml_node_get_content $result_count_node]
        set result_count($result$count
    }
    set test(result_count) [array get result_count]

    # Get counts for failing test cases
    foreach testcase_failure_node [xml_node_get_children_by_name $root_node testcase_failure] {
        set testcase_id [xml_node_get_attribute $testcase_failure_node testcase_id]
        set count [xml_node_get_content $testcase_failure_node]
        set testcase_failure($testcase_id$count
    }
    set test(testcase_failure) [array get testcase_failure]
}

d_proc -public aa_get_first_url {
    {-package_key:required}
} {
    Procedure for getting the URL of a mounted package with the
    package_key. It uses the first instance that it founds. This is
    useful for tclwebtest tests.
} {
    set url [site_node::get_package_url -package_key $package_key]
    if {$url eq ""} {
        site_node::instantiate_and_mount -package_key $package_key
        set url [site_node::get_package_url -package_key $package_key]
    }

    return $url
}

d_proc -public aa_display_result {
    {-response:required}
    {-explanation:required}
} {
    Displays either a pass or fail result with specified explanation
    depending on the given response.

    @param response A boolean value where true (or 1, etc) corresponds
    to a pass result, otherwise the result is a fail.
    @param explanation An explanation accompanying the response.
} {
    if {$response} {
        aa_log_result "pass" "[aa_indent$explanation"
    } else {
        aa_log_result "fail" "[aa_indent$explanation"
    }
}

ad_proc -private aa_used_application_memory {} {

    Return the currently used application memory.  This function
    depends on the usage of TCMalloc from Google Performance Tools.

} {
    if {[::acs::icanuse "ns_info meminfo"]} {
        set mem_info [ns_info meminfo] 
        dict with mem_info {
            # check for a line looking in the TCMalloc result like:
            #
            #   MALLOC:     2531634144 ( 2414.4 MiB) Bytes in use by application
            #
            if {[info exists stats] && [regexp {\nMALLOC:\s+(\d+)\s} $stats . bytes]} {
                set old_value [nsv_set -reset aa_test application_memory $bytes]
                if {$old_value ne ""} {
                    return [list current $bytes diff [expr {$bytes - $old_value}]]
                }
            }
        }
    }
}

ad_proc -public aa_check_leftovers {-silent:boolean {msg final}} {
    #
    # Perform cleanup tests to check for object/command leaks in
    # either the called functions or in the test itself.
    #
} {
    if {[namespace which ::xo::at_cleanup] ne ""} {
        ::xo::at_cleanup
    }

    set domNodes   [list {*}[info commands domNode0*] {*}[info commands domDoc0x*]]
    set xotclObjs  [::xotcl::Object info instances -closure]
    set nxObjs     [::nx::Object info instances  -closure]
    set tmpObjs    [info commands ::nsf::__#*]
    set nsSets     [expr {[acs::icanuse "ns_set stats"] ? [list [ns_set stats]] : [llength [ns_set list]]}]

    dict set stats tdom    [llength $domNodes]
    dict set stats nssets  [llength $nsSets]
    dict set stats xotcl   [llength $xotclObjs]
    dict set stats nx      [llength $nxObjs]
    dict set stats tmpobjs [llength $tmpObjs]

    dict with stats {
        aa_equals "$msg leftover temp objects"     $tmpobjs 0
        if {$tmpobjs > 0} {
            foreach obj $tmpObjs {
                set isXotcl [::nsf::dispatch $obj ::nsf::methods::object::info::hastype ::xotcl::Object]
                set isNx    [::nsf::dispatch $obj ::nsf::methods::object::info::hastype ::nx::Object]
                aa_log obj $obj (isXotcl $isXotcl isNx $isNx)
                aa_log <pre>[$obj serialize]</pre>
                $obj destroy
            }
        }
        aa_equals "$msg leftover tdom cmds"        $tdom 0
        foreach n $domNodes {
            if {[string match domDoc0x* $n]} {
                aa_log node:$n\n<pre>[ns_quotehtml [$n asXML -indent 4]]</pre>
                $n delete
            }
        }
        if {$silent_p} {
            aa_log    "$msg XOTcl objects: $xotcl nx objects: $nx nssets: $nssets"
            set mem_info [aa_used_application_memory]
            if {$mem_info ne ""} {
                dict with mem_info {
                    aa_log "current memory: [format %.6f [expr {$current/1000000.0}]] MB " \
                        "difference to begin of this case: [format %.3f [expr {$diff/1000.0}]] KB"
                }
            }
        }
    }
}

d_proc -public aa_silence_log_entries {
    -severities:required
    code
} {

    Silence expected messages in the system log. The proc deactivates
    the specified severity levels during the code in the last argument
    is executed. After it has finished, the severity levels are reset
    to their previous values.

} {
    set old_severity_values [lmap severity $severities {ns_logctl severity $severity 0}]
     try {
         set result [uplevel $code]
    } finally {
        foreach severity $severities old_severity_value $old_severity_values {
            ns_logctl severity $severity $old_severity_value
        }
    }
    return $result
}

ad_proc -private aa_selenium_init {} {
    Setup a global Selenium RC server connection

    @return true is everything is ok, false if there was any error
} {
    # check if the global selenium connection already exists
    global _acs_automated_testing_selenium_init
    if {[info exists _acs_automated_testing_selenium_init]} {
        # if we already initialized Selenium RC this will be true if
        # we already failed to initialize Selenium RC this will be
        # false. We don't want to try to initialize Selenium RC more
        # than once per request thread in any case so just return the
        # previous status. This is a global and is reset on every
        # request.
        return $_acs_automated_testing_selenium_init
    }

    set server_url [parameter::get_from_package_key \
                        -package_key acs-automated-testing \
                        -parameter "SeleniumRcServer" \
                        -default ""]
    if {$server_url eq ""} {
        # no server configured so don't try to initialize
        return 0
    }
    set server_port [parameter::get_from_package_key \
                         -package_key acs-automated-testing \
                         -parameter "SeleniumRcPort" \
                         -default "4444"]
    set browsers [parameter::get_from_package_key \
                      -package_key acs-automated-testing \
                      -parameter "SeleniumRcBrowsers" \
                      -default "*firefox"]
    set success_p [expr {![catch {::acs::test::selenium::Se init $server_url $server_port ${browsers} [ad_url]} errmsg]}]
    if {!$success_p} {
        ns_log error [ad_log_stack_trace]
    }
    set _acs_automated_testing_selenium_init $success_p
    return $success_p
}

aa_register_init_class \
    "selenium" \
    "Init Class for Selenium Remote Control" \
    {aa_selenium_init} \
    {catch {::acs::test::selenium::Se stop} errmsg}

#
# Local variables:
#    mode: tcl
#    tcl-indent-level: 4
#    indent-tabs-mode: nil
# End: