Commit 07cd21512f for openssl.org

commit 07cd21512f5750a4d9f41c3f8bb0959b7e5aee82
Author: Jakub Zelenka <jakub.zelenka@openssl.foundation>
Date:   Fri May 15 17:22:35 2026 +0200

    Test s_time with new -testmode option

    Adds -testmode to s_time, mirroring the option in openssl speed.
    It bypasses the -time window and runs a minimal number of iterations
    (1 for new connections, 2 for session reuse).

    Adds test_stime covering the new, reuse, and TLSv1.2/TLSv1.3 paths.

    Reviewed-by: Tom Cosgrove <tom.cosgrove@arm.com>
    Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
    MergeDate: Mon Jun 15 13:53:39 2026
    (Merged from https://github.com/openssl/openssl/pull/31192)

diff --git a/CHANGES.md b/CHANGES.md
index 1bfdba4035..70fd8481db 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -31,6 +31,10 @@ OpenSSL Releases

 ### Changes between 4.0 and 4.1 [xx XXX xxxx]

+ * Added -testmode option for `s_time` app.
+
+   *Jakub Zelenka*
+
  * Added support for Ed25519 and Ed448 certificates in DTLS 1.2. Previously,
    these certificate types were only supported in TLS 1.2 and TLS 1.3.

diff --git a/apps/s_time.c b/apps/s_time.c
index 78df236735..59f94cddf8 100644
--- a/apps/s_time.c
+++ b/apps/s_time.c
@@ -66,12 +66,14 @@ typedef enum OPTION_choice {
     OPT_TLS1_1,
     OPT_TLS1_2,
     OPT_TLS1_3,
+    OPT_TESTMODE,
     OPT_PROV_ENUM
 } OPTION_CHOICE;

 const OPTIONS s_time_options[] = {
     OPT_SECTION("General"),
     { "help", OPT_HELP, '-', "Display this summary" },
+    { "testmode", OPT_TESTMODE, '-', "Run the s_time command in test mode" },

     OPT_SECTION("Connection"),
     { "connect", OPT_CONNECT, 's',
@@ -143,7 +145,7 @@ int s_time_main(int argc, char **argv)
     long bytes_read = 0, finishtime = 0;
     OPTION_CHOICE o;
     int min_version = 0, max_version = 0, ver, buf_len, fd;
-    int want_verify = 0;
+    int want_verify = 0, testmode = 0;
     size_t buf_size;

     meth = TLS_client_method();
@@ -239,6 +241,9 @@ int s_time_main(int argc, char **argv)
             min_version = TLS1_3_VERSION;
             max_version = TLS1_3_VERSION;
             break;
+        case OPT_TESTMODE:
+            testmode = 1;
+            break;
         case OPT_PROV_CASES:
             if (!opt_provider(o))
                 goto end;
@@ -287,7 +292,8 @@ int s_time_main(int argc, char **argv)

     if (!(perform & 1))
         goto next;
-    printf("Collecting connection statistics for %d seconds\n", maxtime);
+    if (!testmode)
+        printf("Collecting connection statistics for %d seconds\n", maxtime);

     /* Loop and time how long it takes to make connections */

@@ -295,7 +301,7 @@ int s_time_main(int argc, char **argv)
     finishtime = (long)time(NULL) + maxtime;
     tm_Time_F(START);
     for (;;) {
-        if (finishtime < (long)time(NULL))
+        if (testmode ? nConn >= 1 : finishtime < (long)time(NULL))
             break;

         if ((scon = doConnection(NULL, host, ctx)) == NULL)
@@ -345,7 +351,8 @@ next:
         ret = 0;
         goto end;
     }
-    printf("\n\nNow timing with session id reuse.\n");
+    if (!testmode)
+        printf("\n\nNow timing with session id reuse.\n");

     /* Get an SSL object so we can reuse the session id */
     if ((scon = doConnection(NULL, host, ctx)) == NULL) {
@@ -369,12 +376,13 @@ next:

     finishtime = (long)time(NULL) + maxtime;

-    printf("starting\n");
+    if (!testmode)
+        printf("starting\n");
     bytes_read = 0;
     tm_Time_F(START);

     for (;;) {
-        if (finishtime < (long)time(NULL))
+        if (testmode ? nConn >= 2 : finishtime < (long)time(NULL))
             break;

         if ((doConnection(scon, host, ctx)) == NULL)
diff --git a/doc/man1/openssl-s_time.pod.in b/doc/man1/openssl-s_time.pod.in
index 23f9d48cba..32b104d0ab 100644
--- a/doc/man1/openssl-s_time.pod.in
+++ b/doc/man1/openssl-s_time.pod.in
@@ -17,6 +17,7 @@ B<openssl> B<s_time>
 [B<-new>]
 [B<-verify> I<depth>]
 [B<-time> I<seconds>]
+[B<-testmode>]
 [B<-tls1>]
 [B<-tls1_1>]
 [B<-tls1_2>]
@@ -117,6 +118,13 @@ and optionally transfer payload data from a server. Server and client
 performance and the link speed determine how many connections it
 can establish.

+=item B<-testmode>
+
+Runs the s_time command in test mode. Performs only 1 iteration of the new
+connection test and 2 iterations of the session reuse test (the latter to
+ensure session resumption is actually exercised) regardless of any B<-time>
+value. This is intended for use by the test suite.
+
 {- $OpenSSL::safe::opt_name_item -}

 {- $OpenSSL::safe::opt_trust_item -}
@@ -190,6 +198,8 @@ fails.

 The B<-cafile> option was deprecated in OpenSSL 3.0.

+The B<-testmode> option was added in OpenSSL 4.1.
+
 =head1 SEE ALSO

 L<openssl(1)>,
@@ -200,7 +210,7 @@ L<ossl_store-file(7)>

 =head1 COPYRIGHT

-Copyright 2004-2025 The OpenSSL Project Authors. All Rights Reserved.
+Copyright 2004-2026 The OpenSSL Project Authors. All Rights Reserved.

 Licensed under the Apache License 2.0 (the "License").  You may not use
 this file except in compliance with the License.  You can obtain a copy
diff --git a/test/recipes/70-test_stime.t b/test/recipes/70-test_stime.t
new file mode 100644
index 0000000000..0717224266
--- /dev/null
+++ b/test/recipes/70-test_stime.t
@@ -0,0 +1,78 @@
+#! /usr/bin/env perl
+# Copyright 2026 The OpenSSL Project Authors. All Rights Reserved.
+#
+# Licensed under the Apache License 2.0 (the "License").  You may not use
+# this file except in compliance with the License.  You can obtain a copy
+# in the file LICENSE in the source distribution or at
+# https://www.openssl.org/source/license.html
+
+use strict;
+use warnings;
+
+use IPC::Open3;
+use OpenSSL::Test qw/:DEFAULT srctop_file bldtop_file/;
+use OpenSSL::Test::Utils;
+
+my $test_name = "test_stime";
+setup($test_name);
+
+plan skip_all => "$test_name is not available on $^O"
+    if $^O =~ /^(VMS|MSWin32|msys)$/;
+plan skip_all => "$test_name needs the sock feature enabled"
+    if disabled("sock");
+plan skip_all => "$test_name needs some TLS protocols to be enabled"
+    if alldisabled(available_protocols("tls"));
+plan skip_all => "$test_name needs ec, ecx or dh for TLS key exchange"
+    if disabled("ec") && disabled("ecx") && disabled("dh");
+
+my $shlib_wrap   = bldtop_file("util", "wrap.pl");
+my $apps_openssl = bldtop_file("apps", "openssl");
+my $server_pem   = srctop_file("apps", "server.pem");
+
+plan tests => 4;
+
+my @srv_cmd = ("s_server", "-accept", "0", "-cert", $server_pem);
+my $srv_pid = open3(my $srv_in, my $srv_out, undef,
+                    $shlib_wrap, $apps_openssl, @srv_cmd);
+
+my $port = "0";
+while (<$srv_out>) {
+    chomp;
+    if    (/^ACCEPT 0\.0\.0\.0:(\d+)/) { $port = $1; last; }
+    elsif (/^ACCEPT \[.*\]:(\d+)/)     { $port = $1; last; }
+    elsif (/^Using default/)           { ; }
+    else                               { last; }
+}
+
+close $srv_out;
+
+SKIP: {
+    skip "Could not start s_server", 4 if $port eq "0";
+
+    my $connect = "localhost:$port";
+
+    ok(run(app(["openssl", "s_time",
+                "-connect", $connect, "-new", "-testmode"])),
+       "s_time new connections");
+    ok(run(app(["openssl", "s_time",
+                "-connect", $connect, "-reuse", "-testmode"])),
+       "s_time session reuse");
+
+    SKIP: {
+        skip "TLS 1.2 disabled", 1 if disabled("tls1_2");
+        ok(run(app(["openssl", "s_time",
+                    "-connect", $connect, "-new", "-tls1_2", "-testmode"])),
+           "s_time TLSv1.2 new connections");
+    }
+
+    SKIP: {
+        skip "TLS 1.3 disabled", 1 if disabled("tls1_3");
+        ok(run(app(["openssl", "s_time",
+                    "-connect", $connect, "-new", "-tls1_3", "-testmode"])),
+           "s_time TLSv1.3 new connections");
+    }
+}
+
+close $srv_in;
+kill 'HUP', $srv_pid;
+waitpid($srv_pid, 0);