Compare commits
83
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2021349249 | ||
|
|
e006418946 | ||
|
|
2881b5c3df | ||
|
|
e0209e39ea | ||
|
|
e1196c53f2 | ||
|
|
56b431c4c2 | ||
|
|
ac4291f473 | ||
|
|
bbdb1f12a0 | ||
|
|
f9a4d9f781 | ||
|
|
1a664df87f | ||
|
|
cc4dae5c0f | ||
|
|
8985ee2114 | ||
|
|
85aac3aef7 | ||
|
|
c3c71b5f3c | ||
|
|
9feb8b7c29 | ||
|
|
50a1375461 | ||
|
|
3d5f129cb0 | ||
|
|
6cdb3193ec | ||
|
|
bf46b6a1da | ||
|
|
d5583552c0 | ||
|
|
338b9ec9d2 | ||
|
|
6d3aeef45e | ||
|
|
63ef0e3d26 | ||
|
|
39318d91b7 | ||
|
|
d24a3eb2ba | ||
|
|
cdce5491b3 | ||
|
|
9a15a1b586 | ||
|
|
95db69f27b | ||
|
|
e80eaec4d3 | ||
|
|
b9577c0641 | ||
|
|
cb9f3f9268 | ||
|
|
6d88b01d1e | ||
|
|
77a26f5c95 | ||
|
|
9cec6bac27 | ||
|
|
2f19b0f346 | ||
|
|
265c9d416f | ||
|
|
db610f91eb | ||
|
|
afb5bf5583 | ||
|
|
9b7ce068fe | ||
|
|
820a737a2f | ||
|
|
c2d61d8dbc | ||
|
|
fb0bf9d1b9 | ||
|
|
01dd016ad8 | ||
|
|
d660b325b5 | ||
|
|
26b477b907 | ||
|
|
89ff80fbdd | ||
|
|
90afac281e | ||
|
|
aed158b518 | ||
|
|
754bb107b4 | ||
|
|
227db01958 | ||
|
|
41954059c4 | ||
|
|
b169c964e6 | ||
|
|
3771e638b0 | ||
|
|
4588d7300e | ||
|
|
d690b84738 | ||
|
|
6bc8c27b7f | ||
|
|
6a58a0cab2 | ||
|
|
1357172367 | ||
|
|
466fd1d930 | ||
|
|
441e4f6796 | ||
|
|
3117af219f | ||
|
|
a0e1b18138 | ||
|
|
70af5c2b82 | ||
|
|
d38ae8f974 | ||
|
|
0d887fa465 | ||
|
|
e9bb0992b0 | ||
|
|
f35c0dc2ed | ||
|
|
e80e214673 | ||
|
|
731f40c6d9 | ||
|
|
767cf6314a | ||
|
|
b00317d174 | ||
|
|
a8408767a4 | ||
|
|
4f8b177250 | ||
|
|
25aad1ab18 | ||
|
|
3b48db4a90 | ||
|
|
58eff8f224 | ||
|
|
97721e9675 | ||
|
|
d88d33f74d | ||
|
|
0a9706f48f | ||
|
|
45f91441a5 | ||
|
|
38781266d3 | ||
|
|
843d29b448 | ||
|
|
1a73baff09 |
+17
@@ -217,6 +217,19 @@ information is instead sent to the console, if one is present, or is
|
||||
discarded.
|
||||
.RS
|
||||
.PP
|
||||
Note that the current escape resp. quoting of arguments for windows works only
|
||||
with executables using CommandLineToArgv, CRT-library or similar, as well as
|
||||
with the windows batch files (excepting the newline, see below).
|
||||
Although it is the common escape algorithm, but, in fact, the way how the
|
||||
executable parses the command-line (resp. splits it into single arguments)
|
||||
is decisive.
|
||||
.PP
|
||||
Unfortunately, there is currently no way to supply newline character within
|
||||
an argument to the batch files (\fB.cmd\fR or \fB.bat\fR) or to the command
|
||||
processor (\fBcmd.exe /c\fR), because this causes truncation of command-line
|
||||
(also the argument chain) on the first newline character.
|
||||
But it works properly with an executable (using CommandLineToArgv, etc).
|
||||
.PP
|
||||
The Tk console text widget does not provide real standard IO capabilities.
|
||||
Under Tk, when redirecting from standard input, all applications will see an
|
||||
immediate end-of-file; information redirected to standard output or standard
|
||||
@@ -409,6 +422,10 @@ that sometimes pop up:
|
||||
With the file \fIcmp.bat\fR looking something like:
|
||||
.PP
|
||||
.CS
|
||||
@gcc %*
|
||||
.CE
|
||||
or like another variant using single parameters:
|
||||
.CS
|
||||
@gcc %1 %2 %3 %4 %5 %6 %7 %8 %9
|
||||
.CE
|
||||
.SS "WORKING WITH COMMAND BUILT-INS"
|
||||
|
||||
+177
-9
@@ -6,14 +6,14 @@
|
||||
'\" See the file "license.terms" for information on usage and redistribution
|
||||
'\" of this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
||||
'\"
|
||||
.TH "http" n 2.7 http "Tcl Bundled Packages"
|
||||
.TH "http" n 2.9 http "Tcl Bundled Packages"
|
||||
.so man.macros
|
||||
.BS
|
||||
'\" Note: do not modify the .SH NAME line immediately below!
|
||||
.SH NAME
|
||||
http \- Client-side implementation of the HTTP/1.1 protocol
|
||||
.SH SYNOPSIS
|
||||
\fBpackage require http ?2.7?\fR
|
||||
\fBpackage require http ?2.8?\fR
|
||||
.\" See Also -useragent option documentation in body!
|
||||
.sp
|
||||
\fB::http::config ?\fI\-option value\fR ...?
|
||||
@@ -22,6 +22,8 @@ http \- Client-side implementation of the HTTP/1.1 protocol
|
||||
.sp
|
||||
\fB::http::formatQuery\fR \fIkey value\fR ?\fIkey value\fR ...?
|
||||
.sp
|
||||
\fB::http::quoteString\fR \fIvalue\fR
|
||||
.sp
|
||||
\fB::http::reset\fR \fItoken\fR ?\fIwhy\fR?
|
||||
.sp
|
||||
\fB::http::wait \fItoken\fR
|
||||
@@ -44,12 +46,14 @@ http \- Client-side implementation of the HTTP/1.1 protocol
|
||||
.sp
|
||||
\fB::http::register \fIproto port command\fR
|
||||
.sp
|
||||
\fB::http::registerError \fIport\fR ?\fImessage\fR?
|
||||
.sp
|
||||
\fB::http::unregister \fIproto\fR
|
||||
.BE
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
The \fBhttp\fR package provides the client side of the HTTP/1.1
|
||||
protocol, as defined in RFC 2616.
|
||||
protocol, as defined in RFC 7230 to RFC 7235, which supersede RFC 2616.
|
||||
The package implements the GET, POST, and HEAD operations
|
||||
of HTTP/1.1. It allows configuration of a proxy host to get through
|
||||
firewalls. The package is compatible with the \fBSafesock\fR security
|
||||
@@ -95,6 +99,19 @@ comma-separated list of mime type patterns that you are
|
||||
willing to receive. For example,
|
||||
.QW "image/gif, image/jpeg, text/*" .
|
||||
.TP
|
||||
\fB\-pipeline\fR \fIboolean\fR
|
||||
.
|
||||
Specifies whether HTTP/1.1 transactions on a persistent socket will be
|
||||
pipelined. See the \fBPERSISTENT SOCKETS\fR section for details. The default
|
||||
is 1.
|
||||
.TP
|
||||
\fB\-postfresh\fR \fIboolean\fR
|
||||
.
|
||||
Specifies whether requests that use the \fBPOST\fR method will always use a
|
||||
fresh socket, overriding the \fB-keepalive\fR option of
|
||||
command \fBhttp::geturl\fR. See the \fBPERSISTENT SOCKETS\fR section for details.
|
||||
The default is 0.
|
||||
.TP
|
||||
\fB\-proxyhost\fR \fIhostname\fR
|
||||
.
|
||||
The name of the proxy host, if any. If this value is the
|
||||
@@ -116,20 +133,47 @@ an empty list. The default filter returns the values of the
|
||||
\fB\-proxyhost\fR and \fB\-proxyport\fR settings if they are
|
||||
non-empty.
|
||||
.TP
|
||||
\fB\-repost\fR \fIboolean\fR
|
||||
.
|
||||
Specifies what to do if a POST request over a persistent connection fails
|
||||
because the server has half-closed the connection. If boolean \fBtrue\fR, the
|
||||
request
|
||||
will be automatically retried; if boolean \fBfalse\fR it will not, and the
|
||||
application
|
||||
that uses \fBhttp::geturl\fR is expected to seek user confirmation before
|
||||
retrying the POST. The value \fBtrue\fR should be used only under certain
|
||||
conditions. See the \fBPERSISTENT SOCKETS\fR section for details. The
|
||||
default is 0.
|
||||
.TP
|
||||
\fB\-urlencoding\fR \fIencoding\fR
|
||||
.
|
||||
The \fIencoding\fR used for creating the x-url-encoded URLs with
|
||||
\fB::http::formatQuery\fR. The default is \fButf-8\fR, as specified by RFC
|
||||
\fB::http::formatQuery\fR and \fB::http::quoteString\fR.
|
||||
The default is \fButf-8\fR, as specified by RFC
|
||||
2718. Prior to http 2.5 this was unspecified, and that behavior can be
|
||||
returned by specifying the empty string (\fB{}\fR), although
|
||||
\fIiso8859-1\fR is recommended to restore similar behavior but without the
|
||||
\fB::http::formatQuery\fR throwing an error processing non-latin-1
|
||||
characters.
|
||||
\fB::http::formatQuery\fR or \fB::http::quoteString\fR
|
||||
throwing an error processing non-latin-1 characters.
|
||||
.TP
|
||||
\fB\-useragent\fR \fIstring\fR
|
||||
.
|
||||
The value of the User-Agent header in the HTTP request. The default is
|
||||
.QW "\fBTcl http client package 2.7\fR" .
|
||||
The value of the User-Agent header in the HTTP request. In an unsafe
|
||||
interpreter, the default value depends upon the operating system, and
|
||||
the version numbers of \fBhttp\fR and \fBTcl\fR, and is (for example)
|
||||
.QW "\fBMozilla/5.0 (Windows; U; Windows NT 10.0) http/2.8.12 Tcl/8.6.8\fR" .
|
||||
A safe interpreter cannot determine its operating system, and so the default
|
||||
in a safe interpreter is to use a Windows 10 value with the current version
|
||||
numbers of \fBhttp\fR and \fBTcl\fR.
|
||||
.TP
|
||||
\fB\-zip\fR \fIboolean\fR
|
||||
.
|
||||
If the value is boolean \fBtrue\fR, then by default requests will send a header
|
||||
.QW "\fBAccept-Encoding: gzip,deflate,compress\fR" .
|
||||
If the value is boolean \fBfalse\fR, then by default this header will not be sent.
|
||||
In either case the default can be overridden for an individual request by
|
||||
supplying a custom \fBAccept-Encoding\fR header in the \fB-headers\fR option
|
||||
of \fBhttp::geturl\fR. The default is 1.
|
||||
.RE
|
||||
.TP
|
||||
\fB::http::geturl\fR \fIurl\fR ?\fIoptions\fR?
|
||||
@@ -227,7 +271,7 @@ Pragma: no-cache
|
||||
.TP
|
||||
\fB\-keepalive\fR \fIboolean\fR
|
||||
.
|
||||
If true, attempt to keep the connection open for servicing
|
||||
If boolean \fBtrue\fR, attempt to keep the connection open for servicing
|
||||
multiple requests. Default is 0.
|
||||
.TP
|
||||
\fB\-method\fR \fItype\fR
|
||||
@@ -334,6 +378,11 @@ encodes the keys and values, and generates one string that has the
|
||||
proper & and = separators. The result is suitable for the
|
||||
\fB\-query\fR value passed to \fB::http::geturl\fR.
|
||||
.TP
|
||||
\fB::http::quoteString\fR \fIvalue\fR
|
||||
.
|
||||
This procedure does x-url-encoding of string. It takes a single argument and
|
||||
encodes it.
|
||||
.TP
|
||||
\fB::http::reset\fR \fItoken\fR ?\fIwhy\fR?
|
||||
.
|
||||
This command resets the HTTP transaction identified by \fItoken\fR, if any.
|
||||
@@ -415,6 +464,17 @@ set token [::http::geturl https://my.secure.site/]
|
||||
.CE
|
||||
.RE
|
||||
.TP
|
||||
\fB::http::registerError\fR \fIport\fR ?\fImessage\fR?
|
||||
.
|
||||
This procedure allows a registered protocol handler to deliver an error
|
||||
message for use by \fBhttp\fR. Calling this command does not raise an
|
||||
error. The command is useful when a registered protocol detects an problem
|
||||
(for example, an invalid TLS certificate) that will cause an error to
|
||||
propagate to \fBhttp\fR. The command allows \fBhttp\fR to provide a
|
||||
precise error message rather than a general one. The command returns the
|
||||
value provided by the last call with argument \fImessage\fR, or the empty
|
||||
string if no such call has been made.
|
||||
.TP
|
||||
\fB::http::unregister\fR \fIproto\fR
|
||||
.
|
||||
This procedure unregisters a protocol handler that was previously
|
||||
@@ -504,6 +564,14 @@ The following elements of
|
||||
the array are supported:
|
||||
.RS
|
||||
.TP
|
||||
\fBbinary\fR
|
||||
.
|
||||
This is boolean \fBtrue\fR if (after decoding any compression specified
|
||||
by the
|
||||
.QW "Content-Encoding"
|
||||
response header) the HTTP response is binary. It is boolean \fBfalse\fR
|
||||
if the HTTP response is text.
|
||||
.TP
|
||||
\fBbody\fR
|
||||
.
|
||||
The contents of the URL. This will be empty if the \fB\-channel\fR
|
||||
@@ -602,6 +670,106 @@ A copy of the \fBContent-Type\fR meta-data value.
|
||||
.
|
||||
The requested URL.
|
||||
.RE
|
||||
.SH "PERSISTENT CONNECTIONS"
|
||||
.PP
|
||||
.SS "BASICS"
|
||||
.PP
|
||||
See RFC 7230 Sec 6, which supersedes RFC 2616 Sec 8.1.
|
||||
.PP
|
||||
A persistent connection allows multiple HTTP/1.1 transactions to be
|
||||
carried over the same TCP connection. Pipelining allows a
|
||||
client to make multiple requests over a persistent connection without
|
||||
waiting for each response. The server sends responses in the same order
|
||||
that the requests were received.
|
||||
.PP
|
||||
If a POST request fails to complete, typically user confirmation is
|
||||
needed before sending the request again. The user may wish to verify
|
||||
whether the server was modified by the failed POST request, before
|
||||
sending the same request again.
|
||||
.PP
|
||||
A HTTP request will use a persistent socket if the call to
|
||||
\fBhttp::geturl\fR has the option \fB-keepalive true\fR. It will use
|
||||
pipelining where permitted if the \fBhttp::config\fR option
|
||||
\fB-pipeline\fR is boolean \fBtrue\fR (its default value).
|
||||
.PP
|
||||
The http package maintains no more than one persistent connection to each
|
||||
server (i.e. each value of
|
||||
.QW "domain:port" ).
|
||||
If \fBhttp::geturl\fR is called to make a request over a persistent
|
||||
connection while the connection is busy with another request, the new
|
||||
request will be held in a queue until the connection is free.
|
||||
.PP
|
||||
The http package does not support HTTP/1.0 persistent connections
|
||||
controlled by the \fBKeep-Alive\fR header.
|
||||
.SS "SPECIAL CASES"
|
||||
.PP
|
||||
This subsection discusses issues related to closure of the
|
||||
persistent connection by the server, automatic retry of failed requests,
|
||||
the special treatment necessary for POST requests, and the options for
|
||||
dealing with these cases.
|
||||
.PP
|
||||
In accordance with RFC 7230, \fBhttp::geturl\fR does not pipeline
|
||||
requests that use the POST method. If a POST uses a persistent
|
||||
connection and is not the first request on that connection,
|
||||
\fBhttp::geturl\fR waits until it has received the response for the previous
|
||||
request; or (if \fBhttp::config\fR option \fB-postfresh\fR is boolean \fBtrue\fR) it
|
||||
uses a new connection for each POST.
|
||||
.PP
|
||||
If the server is processing a number of pipelined requests, and sends a
|
||||
response header
|
||||
.QW "\fBConnection: close\fR"
|
||||
with one of the responses (other than the last), then subsequent responses
|
||||
are unfulfilled. \fBhttp::geturl\fR will send the unfulfilled requests again
|
||||
over a new connection.
|
||||
.PP
|
||||
A difficulty arises when a HTTP client sends a request over a persistent
|
||||
connection that has been idle for a while. The HTTP server may
|
||||
half-close an apparently idle connection while the client is sending a
|
||||
request, but before the request arrives at the server: in this case (an
|
||||
.QW "asynchronous close event" )
|
||||
the request will fail. The difficulty arises because the client cannot
|
||||
be certain whether the POST modified the state of the server. For HEAD or
|
||||
GET requests, \fBhttp::geturl\fR opens another connection and retransmits
|
||||
the failed request. However, if the request was a POST, RFC 7230 forbids
|
||||
automatic retry by default, suggesting either user confirmation, or
|
||||
confirmation by user-agent software that has semantic understanding of
|
||||
the application. The \fBhttp::config\fR option \fB-repost\fR allows for
|
||||
either possibility.
|
||||
.PP
|
||||
Asynchronous close events can occur only in a short interval of time. The
|
||||
\fBhttp\fR package monitors each persistent connection for closure by the
|
||||
server. Upon detection, the connection is also closed at the client end,
|
||||
and subsequent requests will use a fresh connection.
|
||||
.PP
|
||||
If the \fBhttp::geturl\fR command is called with option \fB-keepalive true\fR,
|
||||
then it will both try to use an existing persistent connection
|
||||
(if one is available), and it will send the server a
|
||||
.QW "\fBConnection: keep-alive\fR"
|
||||
request header asking to keep the connection open for future requests.
|
||||
.PP
|
||||
The \fBhttp::config\fR options \fB-pipeline\fR, \fB-postfresh\fR, and
|
||||
\fB-repost\fR relate to persistent connections.
|
||||
.PP
|
||||
Option \fB-pipeline\fR, if boolean \fBtrue\fR, will pipeline GET and HEAD requests
|
||||
made
|
||||
over a persistent connection. POST requests will not be pipelined - if the
|
||||
POST is not the first transaction on the connection, its request will not
|
||||
be sent until the previous response has finished. GET and HEAD requests
|
||||
made after a POST will not be sent until the POST response has been
|
||||
delivered, and will not be sent if the POST fails.
|
||||
.PP
|
||||
Option \fB-postfresh\fR, if boolean \fBtrue\fR, will override the \fBhttp::geturl\fR option
|
||||
\fB-keepalive\fR, and always open a fresh connection for a POST request.
|
||||
.PP
|
||||
Option \fB-repost\fR, if \fBtrue\fR, permits automatic retry of a POST request
|
||||
that fails because it uses a persistent connection that the server has
|
||||
half-closed (an
|
||||
.QW "asynchronous close event" ).
|
||||
Subsequent GET and HEAD requests in a failed pipeline will also be retried.
|
||||
\fIThe -repost option should be used only if the application understands
|
||||
that the retry is appropriate\fR - specifically, the application must know
|
||||
that if the failed POST successfully modified the state of the server, a repeat POST
|
||||
would have no adverse effect.
|
||||
.SH EXAMPLE
|
||||
.PP
|
||||
This example creates a procedure to copy a URL to a file while printing a
|
||||
|
||||
+30
-10
@@ -17,7 +17,7 @@ lreplace \- Replace elements in a list with new elements
|
||||
.BE
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
\fBlreplace\fR returns a new list formed by replacing one or more elements of
|
||||
\fBlreplace\fR returns a new list formed by replacing zero or more elements of
|
||||
\fIlist\fR with the \fIelement\fR arguments.
|
||||
\fIfirst\fR and \fIlast\fR are index values specifying the first and
|
||||
last elements of the range to replace.
|
||||
@@ -27,23 +27,26 @@ supporting simple index arithmetic and indices relative to the
|
||||
end of the list.
|
||||
0 refers to the first element of the
|
||||
list, and \fBend\fR refers to the last element of the list.
|
||||
If \fIlist\fR is empty, then \fIfirst\fR and \fIlast\fR are ignored.
|
||||
.PP
|
||||
If \fIfirst\fR is less than zero, it is considered to refer to before the
|
||||
first element of the list. For non-empty lists, the element indicated
|
||||
by \fIfirst\fR must exist or \fIfirst\fR must indicate before the
|
||||
start of the list.
|
||||
If either \fIfirst\fR or \fIlast\fR is less than zero, it is considered
|
||||
to refer to before the first element of the list. This allows \fBlreplace\fR
|
||||
to prepend elements to \fIlist\fR.
|
||||
.VS TIP505
|
||||
If either \fIfirst\fR or \fIlast\fR indicates a position greater than the
|
||||
index of the last element of the list, it is treated as if it is an
|
||||
index one greater than the last element. This allows \fBlreplace\fR to
|
||||
append elements to \fIlist\fR.
|
||||
.VE TIP505
|
||||
.PP
|
||||
If \fIlast\fR is less than \fIfirst\fR, then any specified elements
|
||||
will be inserted into the list before the point specified by \fIfirst\fR
|
||||
will be inserted into the list before the element specified by \fIfirst\fR
|
||||
with no elements being deleted.
|
||||
.PP
|
||||
The \fIelement\fR arguments specify zero or more new arguments to
|
||||
The \fIelement\fR arguments specify zero or more new elements to
|
||||
be added to the list in place of those that were deleted.
|
||||
Each \fIelement\fR argument will become a separate element of
|
||||
the list. If no \fIelement\fR arguments are specified, then the elements
|
||||
between \fIfirst\fR and \fIlast\fR are simply deleted. If \fIlist\fR
|
||||
is empty, any \fIelement\fR arguments are added to the end of the list.
|
||||
between \fIfirst\fR and \fIlast\fR are simply deleted.
|
||||
.SH EXAMPLES
|
||||
.PP
|
||||
Replacing an element of a list with another:
|
||||
@@ -78,9 +81,26 @@ proc lremove {listVariable value} {
|
||||
set var [\fBlreplace\fR $var $idx $idx]
|
||||
}
|
||||
.CE
|
||||
.PP
|
||||
.VS TIP505
|
||||
Appending elements to the list; note that \fBend+2\fR will initially
|
||||
be treated as if it is \fB6\fR here, but both that and \fB12345\fR are greater
|
||||
than the index of the final item so they behave identically:
|
||||
.PP
|
||||
.CS
|
||||
% set var {a b c d e}
|
||||
a b c d e
|
||||
% set var [\fBlreplace\fR $var 12345 end+2 f g h i]
|
||||
a b c d e f g h i
|
||||
.CE
|
||||
.VE TIP505
|
||||
.SH "SEE ALSO"
|
||||
list(n), lappend(n), lindex(n), linsert(n), llength(n), lsearch(n),
|
||||
lset(n), lrange(n), lsort(n),
|
||||
string(n)
|
||||
.SH KEYWORDS
|
||||
element, list, replace
|
||||
.\" Local variables:
|
||||
.\" mode: nroff
|
||||
.\" fill-column: 78
|
||||
.\" End:
|
||||
|
||||
+13
-6
@@ -7606,7 +7606,19 @@ ExprEntierFunc(
|
||||
|
||||
if (type == TCL_NUMBER_DOUBLE) {
|
||||
d = *((const double *) ptr);
|
||||
if ((d >= (double)LONG_MAX) || (d <= (double)LONG_MIN)) {
|
||||
if ((d < (double)LONG_MAX) && (d > (double)LONG_MIN)) {
|
||||
long result = (long) d;
|
||||
|
||||
Tcl_SetObjResult(interp, Tcl_NewLongObj(result));
|
||||
return TCL_OK;
|
||||
#ifndef TCL_WIDE_INT_IS_LONG
|
||||
} else if ((d < (double)LLONG_MAX) && (d > (double)LLONG_MIN)) {
|
||||
Tcl_WideInt result = (Tcl_WideInt) d;
|
||||
|
||||
Tcl_SetObjResult(interp, Tcl_NewWideIntObj(result));
|
||||
return TCL_OK;
|
||||
#endif
|
||||
} else {
|
||||
mp_int big;
|
||||
|
||||
if (Tcl_InitBignumFromDouble(interp, d, &big) != TCL_OK) {
|
||||
@@ -7615,11 +7627,6 @@ ExprEntierFunc(
|
||||
}
|
||||
Tcl_SetObjResult(interp, Tcl_NewBignumObj(&big));
|
||||
return TCL_OK;
|
||||
} else {
|
||||
long result = (long) d;
|
||||
|
||||
Tcl_SetObjResult(interp, Tcl_NewLongObj(result));
|
||||
return TCL_OK;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-14
@@ -2747,21 +2747,10 @@ Tcl_LreplaceObjCmd(
|
||||
if (first < 0) {
|
||||
first = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Complain if the user asked for a start element that is greater than the
|
||||
* list length. This won't ever trigger for the "end-*" case as that will
|
||||
* be properly constrained by TclGetIntForIndex because we use listLen-1
|
||||
* (to allow for replacing the last elem).
|
||||
*/
|
||||
|
||||
if ((first >= listLen) && (listLen > 0)) {
|
||||
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
||||
"list doesn't contain element %s", TclGetString(objv[2])));
|
||||
Tcl_SetErrorCode(interp, "TCL", "OPERATION", "LREPLACE", "BADIDX",
|
||||
NULL);
|
||||
return TCL_ERROR;
|
||||
if (first > listLen) {
|
||||
first = listLen;
|
||||
}
|
||||
|
||||
if (last >= listLen) {
|
||||
last = listLen - 1;
|
||||
}
|
||||
|
||||
+4
-55
@@ -1472,7 +1472,7 @@ TclCompileLreplaceCmd(
|
||||
{
|
||||
Tcl_Token *tokenPtr, *listTokenPtr;
|
||||
DefineLineInformation; /* TIP #280 */
|
||||
int idx1, idx2, i, offset, offset2;
|
||||
int idx1, idx2, i;
|
||||
int emptyPrefix=1, suffixStart = 0;
|
||||
|
||||
if (parsePtr->numWords < 4) {
|
||||
@@ -1492,23 +1492,6 @@ TclCompileLreplaceCmd(
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* idx1, idx2 are the conventional encoded forms of the tokens parsed
|
||||
* as all forms of index values. Values of idx1 that come before the
|
||||
* list are treated the same as if they were the start of the list.
|
||||
* Values of idx2 that come after the list are treated the same as if
|
||||
* they were the end of the list.
|
||||
*/
|
||||
|
||||
if (idx1 == TCL_INDEX_AFTER) {
|
||||
/*
|
||||
* [lreplace] treats idx1 value end+1 differently from end+2, etc.
|
||||
* The operand encoding cannot distinguish them, so we must bail
|
||||
* out to direct evaluation.
|
||||
*/
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* General structure of the [lreplace] result is
|
||||
* prefix replacement suffix
|
||||
@@ -1520,7 +1503,9 @@ TclCompileLreplaceCmd(
|
||||
* we must defer to direct evaluation.
|
||||
*/
|
||||
|
||||
if (idx2 == TCL_INDEX_BEFORE) {
|
||||
if (idx1 == TCL_INDEX_AFTER) {
|
||||
suffixStart = idx1;
|
||||
} else if (idx2 == TCL_INDEX_BEFORE) {
|
||||
suffixStart = idx1;
|
||||
} else if (idx2 == TCL_INDEX_END) {
|
||||
suffixStart = TCL_INDEX_AFTER;
|
||||
@@ -1552,42 +1537,6 @@ TclCompileLreplaceCmd(
|
||||
emptyPrefix = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* [lreplace] raises an error when idx1 points after the list, but
|
||||
* only when the list is not empty. This is maximum stupidity.
|
||||
*
|
||||
* TODO: TIP this nonsense away!
|
||||
*/
|
||||
if (idx1 >= TCL_INDEX_START) {
|
||||
if (emptyPrefix) {
|
||||
TclEmitOpcode( INST_DUP, envPtr);
|
||||
} else {
|
||||
TclEmitInstInt4( INST_OVER, 1, envPtr);
|
||||
}
|
||||
TclEmitOpcode( INST_LIST_LENGTH, envPtr);
|
||||
TclEmitOpcode( INST_DUP, envPtr);
|
||||
offset = CurrentOffset(envPtr);
|
||||
TclEmitInstInt1( INST_JUMP_FALSE1, 0, envPtr);
|
||||
|
||||
/* List is not empty */
|
||||
TclEmitPush(TclAddLiteralObj(envPtr, Tcl_NewIntObj(idx1),
|
||||
NULL), envPtr);
|
||||
TclEmitOpcode( INST_GT, envPtr);
|
||||
offset2 = CurrentOffset(envPtr);
|
||||
TclEmitInstInt1( INST_JUMP_TRUE1, 0, envPtr);
|
||||
|
||||
/* Idx1 >= list length ===> raise an error */
|
||||
TclEmitPush(TclAddLiteralObj(envPtr, Tcl_ObjPrintf(
|
||||
"list doesn't contain element %d", idx1), NULL), envPtr);
|
||||
CompileReturnInternal(envPtr, INST_RETURN_IMM, TCL_ERROR, 0,
|
||||
Tcl_ObjPrintf("-errorcode {TCL OPERATION LREPLACE BADIDX}"));
|
||||
TclStoreInt1AtPtr(CurrentOffset(envPtr) - offset,
|
||||
envPtr->codeStart + offset + 1);
|
||||
TclEmitOpcode( INST_POP, envPtr);
|
||||
TclStoreInt1AtPtr(CurrentOffset(envPtr) - offset2,
|
||||
envPtr->codeStart + offset2 + 1);
|
||||
}
|
||||
|
||||
if ((idx1 == suffixStart) && (parsePtr->numWords == 4)) {
|
||||
/*
|
||||
* This is a "no-op". Example: [lreplace {a b c} 2 0]
|
||||
|
||||
@@ -1494,7 +1494,7 @@ MODULE_SCOPE int TclPushProcCallFrame(ClientData clientData,
|
||||
#endif
|
||||
|
||||
#define TclGetInt4AtPtr(p) \
|
||||
(((int) TclGetInt1AtPtr(p) << 24) | \
|
||||
(((int) (TclGetUInt1AtPtr(p) << 24)) | \
|
||||
(*((p)+1) << 16) | \
|
||||
(*((p)+2) << 8) | \
|
||||
(*((p)+3)))
|
||||
|
||||
+69
-40
@@ -56,7 +56,6 @@ static const struct {
|
||||
* Function declarations for things defined in this file.
|
||||
*/
|
||||
|
||||
static Class * AllocClass(Tcl_Interp *interp, Object *useThisObj);
|
||||
static Object * AllocObject(Tcl_Interp *interp, const char *nameStr,
|
||||
Namespace *nsPtr, const char *nsNameStr);
|
||||
static int CloneClassMethod(Tcl_Interp *interp, Class *clsPtr,
|
||||
@@ -79,8 +78,6 @@ static void ObjectNamespaceDeleted(ClientData clientData);
|
||||
static void ObjectRenamedTrace(ClientData clientData,
|
||||
Tcl_Interp *interp, const char *oldName,
|
||||
const char *newName, int flags);
|
||||
static void ReleaseClassContents(Tcl_Interp *interp,Object *oPtr);
|
||||
static void DeleteDescendants(Tcl_Interp *interp,Object *oPtr);
|
||||
static inline void SquelchCachedName(Object *oPtr);
|
||||
|
||||
static int PublicObjectCmd(ClientData clientData,
|
||||
@@ -392,10 +389,10 @@ InitFoundation(
|
||||
|
||||
/* Stand up a phony class for bootstrapping. */
|
||||
fPtr->objectCls = &fakeCls;
|
||||
/* referenced in AllocClass to increment the refCount. */
|
||||
/* referenced in TclOOAllocClass to increment the refCount. */
|
||||
fakeCls.thisPtr = &fakeObject;
|
||||
|
||||
fPtr->objectCls = AllocClass(interp,
|
||||
fPtr->objectCls = TclOOAllocClass(interp,
|
||||
AllocObject(interp, "object", (Namespace *)fPtr->ooNs, NULL));
|
||||
/* Corresponding TclOODecrRefCount in KillFoudation */
|
||||
AddRef(fPtr->objectCls->thisPtr);
|
||||
@@ -411,7 +408,7 @@ InitFoundation(
|
||||
fPtr->objectCls->thisPtr->flags |= ROOT_OBJECT;
|
||||
fPtr->objectCls->flags |= ROOT_OBJECT;
|
||||
|
||||
fPtr->classCls = AllocClass(interp,
|
||||
fPtr->classCls = TclOOAllocClass(interp,
|
||||
AllocObject(interp, "class", (Namespace *)fPtr->ooNs, NULL));
|
||||
/* Corresponding TclOODecrRefCount in KillFoudation */
|
||||
AddRef(fPtr->classCls->thisPtr);
|
||||
@@ -829,15 +826,15 @@ ObjectRenamedTrace(
|
||||
/*
|
||||
* ----------------------------------------------------------------------
|
||||
*
|
||||
* DeleteDescendants --
|
||||
* TclOODeleteDescendants --
|
||||
*
|
||||
* Delete all descendants of a particular class.
|
||||
*
|
||||
* ----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
static void
|
||||
DeleteDescendants(
|
||||
void
|
||||
TclOODeleteDescendants(
|
||||
Tcl_Interp *interp, /* The interpreter containing the class. */
|
||||
Object *oPtr) /* The object representing the class. */
|
||||
{
|
||||
@@ -854,7 +851,8 @@ DeleteDescendants(
|
||||
/* This condition also covers the case where mixinSubclassPtr ==
|
||||
* clsPtr
|
||||
*/
|
||||
if (!Deleted(mixinSubclassPtr->thisPtr)) {
|
||||
if (!Deleted(mixinSubclassPtr->thisPtr)
|
||||
&& !(mixinSubclassPtr->thisPtr->flags & DONT_DELETE)) {
|
||||
Tcl_DeleteCommandFromToken(interp,
|
||||
mixinSubclassPtr->thisPtr->command);
|
||||
}
|
||||
@@ -872,8 +870,10 @@ DeleteDescendants(
|
||||
if (clsPtr->subclasses.num > 0) {
|
||||
while (clsPtr->subclasses.num > 0) {
|
||||
subclassPtr = clsPtr->subclasses.list[clsPtr->subclasses.num-1];
|
||||
if (!Deleted(subclassPtr->thisPtr) && !IsRoot(subclassPtr)) {
|
||||
Tcl_DeleteCommandFromToken(interp, subclassPtr->thisPtr->command);
|
||||
if (!Deleted(subclassPtr->thisPtr) && !IsRoot(subclassPtr)
|
||||
&& !(subclassPtr->thisPtr->flags & DONT_DELETE)) {
|
||||
Tcl_DeleteCommandFromToken(interp,
|
||||
subclassPtr->thisPtr->command);
|
||||
}
|
||||
TclOORemoveFromSubclasses(subclassPtr, clsPtr);
|
||||
}
|
||||
@@ -892,7 +892,8 @@ DeleteDescendants(
|
||||
while (clsPtr->instances.num > 0) {
|
||||
instancePtr = clsPtr->instances.list[clsPtr->instances.num-1];
|
||||
/* This condition also covers the case where instancePtr == oPtr */
|
||||
if (!Deleted(instancePtr) && !IsRoot(instancePtr)) {
|
||||
if (!Deleted(instancePtr) && !IsRoot(instancePtr) &&
|
||||
!(instancePtr->flags & DONT_DELETE)) {
|
||||
Tcl_DeleteCommandFromToken(interp, instancePtr->command);
|
||||
}
|
||||
TclOORemoveFromInstances(instancePtr, clsPtr);
|
||||
@@ -909,7 +910,7 @@ DeleteDescendants(
|
||||
/*
|
||||
* ----------------------------------------------------------------------
|
||||
*
|
||||
* ReleaseClassContents --
|
||||
* TclOOReleaseClassContents --
|
||||
*
|
||||
* Tear down the special class data structure, including deleting all
|
||||
* dependent classes and objects.
|
||||
@@ -917,8 +918,8 @@ DeleteDescendants(
|
||||
* ----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
static void
|
||||
ReleaseClassContents(
|
||||
void
|
||||
TclOOReleaseClassContents(
|
||||
Tcl_Interp *interp, /* The interpreter containing the class. */
|
||||
Object *oPtr) /* The object representing the class. */
|
||||
{
|
||||
@@ -940,9 +941,6 @@ ReleaseClassContents(
|
||||
} else if (IsRootObject(oPtr)) {
|
||||
Tcl_Panic("deleting class structure for non-deleted %s",
|
||||
"::oo::object");
|
||||
} else {
|
||||
Tcl_Panic("deleting class structure for non-deleted %s",
|
||||
"general object");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1037,6 +1035,7 @@ ReleaseClassContents(
|
||||
if (IsRootClass(oPtr) && !Deleted(fPtr->objectCls->thisPtr)) {
|
||||
Tcl_DeleteCommandFromToken(interp, fPtr->objectCls->thisPtr->command);
|
||||
}
|
||||
oPtr->classPtr = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1082,7 +1081,7 @@ ObjectNamespaceDeleted(
|
||||
|
||||
/* Let the dominoes fall */
|
||||
if (oPtr->classPtr) {
|
||||
DeleteDescendants(interp, oPtr);
|
||||
TclOODeleteDescendants(interp, oPtr);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1194,27 +1193,25 @@ ObjectNamespaceDeleted(
|
||||
}
|
||||
|
||||
/*
|
||||
* Because an object can be a class that is an instance of itself, the
|
||||
* A class object's class structure should only be cleaned after most of
|
||||
* the cleanup on the object is done.
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Because an object can be a class that is an instance of itself, the
|
||||
* class object's class structure should only be cleaned after most of
|
||||
* the cleanup on the object is done.
|
||||
*
|
||||
* The class of objects needs some special care; if it is deleted (and
|
||||
* we're not killing the whole interpreter) we force the delete of the
|
||||
* class of classes now as well. Due to the incestuous nature of those two
|
||||
* classes, if one goes the other must too and yet the tangle can
|
||||
* sometimes not go away automatically; we force it here. [Bug 2962664]
|
||||
*/
|
||||
|
||||
if (IsRootObject(oPtr) && !Deleted(fPtr->classCls->thisPtr)
|
||||
&& !Tcl_InterpDeleted(interp)) {
|
||||
&& !Tcl_InterpDeleted(interp)) {
|
||||
|
||||
Tcl_DeleteCommandFromToken(interp, fPtr->classCls->thisPtr->command);
|
||||
}
|
||||
|
||||
if (oPtr->classPtr != NULL) {
|
||||
ReleaseClassContents(interp, oPtr);
|
||||
TclOOReleaseClassContents(interp, oPtr);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1325,6 +1322,37 @@ TclOOAddToInstances(
|
||||
AddRef(oPtr);
|
||||
}
|
||||
|
||||
/*
|
||||
* ----------------------------------------------------------------------
|
||||
*
|
||||
* TclOORemoveFromMixins --
|
||||
*
|
||||
* Utility function to remove a class from the list of mixins within an
|
||||
* object.
|
||||
*
|
||||
* ----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int
|
||||
TclOORemoveFromMixins(
|
||||
Class *mixinPtr, /* The mixin to remove. */
|
||||
Object *oPtr) /* The object (possibly) containing the
|
||||
* reference to the mixin. */
|
||||
{
|
||||
int i, res = 0;
|
||||
Class *mixPtr;
|
||||
|
||||
FOREACH(mixPtr, oPtr->mixins) {
|
||||
if (mixinPtr == mixPtr) {
|
||||
RemoveItem(Class, oPtr->mixins, i);
|
||||
TclOODecrRefCount(mixPtr->thisPtr);
|
||||
res++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* ----------------------------------------------------------------------
|
||||
*
|
||||
@@ -1381,7 +1409,8 @@ TclOOAddToSubclasses(
|
||||
if (superPtr->subclasses.size == ALLOC_CHUNK) {
|
||||
superPtr->subclasses.list = ckalloc(sizeof(Class *) * ALLOC_CHUNK);
|
||||
} else {
|
||||
superPtr->subclasses.list = ckrealloc(superPtr->subclasses.list, sizeof(Class *) * superPtr->subclasses.size);
|
||||
superPtr->subclasses.list = ckrealloc(superPtr->subclasses.list,
|
||||
sizeof(Class *) * superPtr->subclasses.size);
|
||||
}
|
||||
}
|
||||
superPtr->subclasses.list[superPtr->subclasses.num++] = subPtr;
|
||||
@@ -1456,16 +1485,16 @@ TclOOAddToMixinSubs(
|
||||
/*
|
||||
* ----------------------------------------------------------------------
|
||||
*
|
||||
* AllocClass --
|
||||
* TclOOAllocClass --
|
||||
*
|
||||
* Allocate a basic class. Does not add class to its
|
||||
* class's instance list.
|
||||
* Allocate a basic class. Does not add class to its class's instance
|
||||
* list.
|
||||
*
|
||||
* ----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
static Class *
|
||||
AllocClass(
|
||||
Class *
|
||||
TclOOAllocClass(
|
||||
Tcl_Interp *interp, /* Interpreter within which to allocate the
|
||||
* class. */
|
||||
Object *useThisObj) /* Object that is to act as the class
|
||||
@@ -1709,13 +1738,13 @@ TclNewObjectInstanceCommon(
|
||||
|
||||
if (TclOOIsReachable(fPtr->classCls, classPtr)) {
|
||||
/*
|
||||
* Is a class, so attach a class structure. Note that the AllocClass
|
||||
* function splices the structure into the object, so we don't have
|
||||
* to. Once that's done, we need to repatch the object to have the
|
||||
* right class since AllocClass interferes with that.
|
||||
* Is a class, so attach a class structure. Note that the
|
||||
* TclOOAllocClass function splices the structure into the object, so
|
||||
* we don't have to. Once that's done, we need to repatch the object
|
||||
* to have the right class since TclOOAllocClass interferes with that.
|
||||
*/
|
||||
|
||||
AllocClass(interp, oPtr);
|
||||
TclOOAllocClass(interp, oPtr);
|
||||
TclOOAddToSubclasses(oPtr->classPtr, fPtr->objectCls);
|
||||
} else {
|
||||
oPtr->classPtr = NULL;
|
||||
|
||||
@@ -1444,7 +1444,6 @@ AddSimpleClassChainToCallContext(
|
||||
if (flags & CONSTRUCTOR) {
|
||||
AddMethodToCallChain(classPtr->constructorPtr, cbPtr, doneFilters,
|
||||
filterDecl, flags);
|
||||
|
||||
} else if (flags & DESTRUCTOR) {
|
||||
AddMethodToCallChain(classPtr->destructorPtr, cbPtr, doneFilters,
|
||||
filterDecl, flags);
|
||||
|
||||
@@ -1083,6 +1083,8 @@ TclOODefineClassObjCmd(
|
||||
{
|
||||
Object *oPtr;
|
||||
Class *clsPtr;
|
||||
Foundation *fPtr = TclOOGetFoundation(interp);
|
||||
int wasClass, willBeClass;
|
||||
|
||||
/*
|
||||
* Parse the context to get the object to operate on.
|
||||
@@ -1118,12 +1120,20 @@ TclOODefineClassObjCmd(
|
||||
if (clsPtr == NULL) {
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
if (oPtr == clsPtr->thisPtr) {
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(
|
||||
"may not change classes into an instance of themselves", -1));
|
||||
Tcl_SetErrorCode(interp, "TCL", "OO", "MONKEY_BUSINESS", NULL);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the object's class.
|
||||
*/
|
||||
|
||||
wasClass = (oPtr->classPtr != NULL);
|
||||
willBeClass = (TclOOIsReachable(fPtr->classCls, clsPtr));
|
||||
|
||||
if (oPtr->selfCls != clsPtr) {
|
||||
TclOORemoveFromInstances(oPtr, oPtr->selfCls);
|
||||
TclOODecrRefCount(oPtr->selfCls->thisPtr);
|
||||
@@ -1131,6 +1141,26 @@ TclOODefineClassObjCmd(
|
||||
AddRef(oPtr->selfCls->thisPtr);
|
||||
TclOOAddToInstances(oPtr, oPtr->selfCls);
|
||||
|
||||
/*
|
||||
* Create or delete the class guts if necessary.
|
||||
*/
|
||||
|
||||
if (wasClass && !willBeClass) {
|
||||
/*
|
||||
* This is the most global of all epochs. Bump it! No cache can be
|
||||
* trusted!
|
||||
*/
|
||||
|
||||
TclOORemoveFromMixins(oPtr->classPtr, oPtr);
|
||||
oPtr->fPtr->epoch++;
|
||||
oPtr->flags |= DONT_DELETE;
|
||||
TclOODeleteDescendants(interp, oPtr);
|
||||
oPtr->flags &= ~DONT_DELETE;
|
||||
TclOOReleaseClassContents(interp, oPtr);
|
||||
} else if (!wasClass && willBeClass) {
|
||||
TclOOAllocClass(interp, oPtr);
|
||||
}
|
||||
|
||||
if (oPtr->classPtr != NULL) {
|
||||
BumpGlobalEpoch(interp, oPtr->classPtr);
|
||||
} else {
|
||||
|
||||
@@ -214,6 +214,7 @@ typedef struct Object {
|
||||
* other spots). */
|
||||
#define FORCE_UNKNOWN 0x10000 /* States that we are *really* looking up the
|
||||
* unknown method handler at that point. */
|
||||
#define DONT_DELETE 0x20000 /* Inhibit deletion of this object. */
|
||||
|
||||
/*
|
||||
* And the definition of a class. Note that every class also has an associated
|
||||
@@ -484,6 +485,8 @@ MODULE_SCOPE int TclOO_Object_VarName(ClientData clientData,
|
||||
MODULE_SCOPE void TclOOAddToInstances(Object *oPtr, Class *clsPtr);
|
||||
MODULE_SCOPE void TclOOAddToMixinSubs(Class *subPtr, Class *mixinPtr);
|
||||
MODULE_SCOPE void TclOOAddToSubclasses(Class *subPtr, Class *superPtr);
|
||||
MODULE_SCOPE Class * TclOOAllocClass(Tcl_Interp *interp,
|
||||
Object *useThisObj);
|
||||
MODULE_SCOPE int TclNRNewObjectInstance(Tcl_Interp *interp,
|
||||
Tcl_Class cls, const char *nameStr,
|
||||
const char *nsNameStr, int objc,
|
||||
@@ -498,6 +501,8 @@ MODULE_SCOPE int TclOODefineSlots(Foundation *fPtr);
|
||||
MODULE_SCOPE void TclOODeleteChain(CallChain *callPtr);
|
||||
MODULE_SCOPE void TclOODeleteChainCache(Tcl_HashTable *tablePtr);
|
||||
MODULE_SCOPE void TclOODeleteContext(CallContext *contextPtr);
|
||||
MODULE_SCOPE void TclOODeleteDescendants(Tcl_Interp *interp,
|
||||
Object *oPtr);
|
||||
MODULE_SCOPE void TclOODelMethodRef(Method *method);
|
||||
MODULE_SCOPE CallContext *TclOOGetCallContext(Object *oPtr,
|
||||
Tcl_Obj *methodNameObj, int flags,
|
||||
@@ -523,7 +528,10 @@ MODULE_SCOPE int TclNRObjectContextInvokeNext(Tcl_Interp *interp,
|
||||
MODULE_SCOPE void TclOONewBasicMethod(Tcl_Interp *interp, Class *clsPtr,
|
||||
const DeclaredClassMethod *dcm);
|
||||
MODULE_SCOPE Tcl_Obj * TclOOObjectName(Tcl_Interp *interp, Object *oPtr);
|
||||
MODULE_SCOPE void TclOOReleaseClassContents(Tcl_Interp *interp,
|
||||
Object *oPtr);
|
||||
MODULE_SCOPE int TclOORemoveFromInstances(Object *oPtr, Class *clsPtr);
|
||||
MODULE_SCOPE int TclOORemoveFromMixins(Class *mixinPtr, Object *oPtr);
|
||||
MODULE_SCOPE int TclOORemoveFromMixinSubs(Class *subPtr,
|
||||
Class *mixinPtr);
|
||||
MODULE_SCOPE int TclOORemoveFromSubclasses(Class *subPtr,
|
||||
|
||||
+23
-21
@@ -504,10 +504,11 @@ TclCreateProc(
|
||||
goto procError;
|
||||
}
|
||||
|
||||
nameLength = Tcl_NumUtfChars(Tcl_GetString(fieldValues[0]), fieldValues[0]->length);
|
||||
argname = Tcl_GetStringFromObj(fieldValues[0], &plen);
|
||||
nameLength = Tcl_NumUtfChars(argname, plen);
|
||||
if (fieldCount == 2) {
|
||||
valueLength = Tcl_NumUtfChars(Tcl_GetString(fieldValues[1]),
|
||||
fieldValues[1]->length);
|
||||
const char * value = TclGetString(fieldValues[1]);
|
||||
valueLength = Tcl_NumUtfChars(value, fieldValues[1]->length);
|
||||
} else {
|
||||
valueLength = 0;
|
||||
}
|
||||
@@ -516,7 +517,6 @@ TclCreateProc(
|
||||
* Check that the formal parameter name is a scalar.
|
||||
*/
|
||||
|
||||
argname = Tcl_GetStringFromObj(fieldValues[0], &plen);
|
||||
argnamei = argname;
|
||||
argnamelast = argname[plen-1];
|
||||
while (plen--) {
|
||||
@@ -611,7 +611,7 @@ TclCreateProc(
|
||||
procPtr->lastLocalPtr = localPtr;
|
||||
}
|
||||
localPtr->nextPtr = NULL;
|
||||
localPtr->nameLength = Tcl_NumUtfChars(argname, fieldValues[0]->length);
|
||||
localPtr->nameLength = nameLength;
|
||||
localPtr->frameIndex = i;
|
||||
localPtr->flags = VAR_ARGUMENT;
|
||||
localPtr->resolveInfo = NULL;
|
||||
@@ -1035,7 +1035,6 @@ ProcWrongNumArgs(
|
||||
{
|
||||
CallFrame *framePtr = ((Interp *)interp)->varFramePtr;
|
||||
register Proc *procPtr = framePtr->procPtr;
|
||||
register Var *defPtr;
|
||||
int localCt = procPtr->numCompiledLocals, numArgs, i;
|
||||
Tcl_Obj **desiredObjs;
|
||||
const char *final = NULL;
|
||||
@@ -1059,23 +1058,26 @@ ProcWrongNumArgs(
|
||||
}
|
||||
Tcl_IncrRefCount(desiredObjs[0]);
|
||||
|
||||
defPtr = (Var *) (&framePtr->localCachePtr->varName0 + localCt);
|
||||
for (i=1 ; i<=numArgs ; i++, defPtr++) {
|
||||
Tcl_Obj *argObj;
|
||||
Tcl_Obj *namePtr = localName(framePtr, i-1);
|
||||
if (localCt > 0) {
|
||||
register Var *defPtr = (Var *) (&framePtr->localCachePtr->varName0 + localCt);
|
||||
|
||||
if (defPtr->value.objPtr != NULL) {
|
||||
TclNewObj(argObj);
|
||||
Tcl_AppendStringsToObj(argObj, "?", TclGetString(namePtr), "?", NULL);
|
||||
} else if (defPtr->flags & VAR_IS_ARGS) {
|
||||
numArgs--;
|
||||
final = "?arg ...?";
|
||||
break;
|
||||
} else {
|
||||
argObj = namePtr;
|
||||
Tcl_IncrRefCount(namePtr);
|
||||
for (i=1 ; i<=numArgs ; i++, defPtr++) {
|
||||
Tcl_Obj *argObj;
|
||||
Tcl_Obj *namePtr = localName(framePtr, i-1);
|
||||
|
||||
if (defPtr->value.objPtr != NULL) {
|
||||
TclNewObj(argObj);
|
||||
Tcl_AppendStringsToObj(argObj, "?", TclGetString(namePtr), "?", NULL);
|
||||
} else if (defPtr->flags & VAR_IS_ARGS) {
|
||||
numArgs--;
|
||||
final = "?arg ...?";
|
||||
break;
|
||||
} else {
|
||||
argObj = namePtr;
|
||||
Tcl_IncrRefCount(namePtr);
|
||||
}
|
||||
desiredObjs[i] = argObj;
|
||||
}
|
||||
desiredObjs[i] = argObj;
|
||||
}
|
||||
|
||||
Tcl_ResetResult(interp);
|
||||
|
||||
+15
-42
@@ -12,6 +12,10 @@
|
||||
#include "tclInt.h"
|
||||
#include "tommath.h"
|
||||
|
||||
#ifdef __CYGWIN__
|
||||
# include <wchar.h>
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC dependency "tcl.decls"
|
||||
#pragma GCC dependency "tclInt.decls"
|
||||
@@ -106,10 +110,6 @@ static unsigned short TclWinNToHS(unsigned short ns) {
|
||||
# define TclWinFlushDirtyChannels doNothing
|
||||
# define TclWinResetInterfaces doNothing
|
||||
|
||||
#if TCL_UTF_MAX < 4
|
||||
static Tcl_Encoding winTCharEncoding;
|
||||
#endif
|
||||
|
||||
static int
|
||||
TclpIsAtty(int fd)
|
||||
{
|
||||
@@ -188,25 +188,11 @@ Tcl_WinUtfToTChar(
|
||||
int len,
|
||||
Tcl_DString *dsPtr)
|
||||
{
|
||||
#if TCL_UTF_MAX > 3
|
||||
WCHAR *wp;
|
||||
int size = MultiByteToWideChar(CP_UTF8, 0, string, len, 0, 0);
|
||||
|
||||
Tcl_DStringInit(dsPtr);
|
||||
Tcl_DStringSetLength(dsPtr, 2*size+2);
|
||||
wp = (WCHAR *)Tcl_DStringValue(dsPtr);
|
||||
MultiByteToWideChar(CP_UTF8, 0, string, len, wp, size+1);
|
||||
if (len == -1) --size; /* account for 0-byte at string end */
|
||||
Tcl_DStringSetLength(dsPtr, 2*size);
|
||||
wp[size] = 0;
|
||||
return (char *)wp;
|
||||
#else
|
||||
if (!winTCharEncoding) {
|
||||
winTCharEncoding = Tcl_GetEncoding(0, "unicode");
|
||||
if (!string) {
|
||||
return NULL;
|
||||
}
|
||||
return Tcl_UtfToExternalDString(winTCharEncoding,
|
||||
string, len, dsPtr);
|
||||
#endif
|
||||
return (char *)Tcl_UtfToUniCharDString(string, len, dsPtr);
|
||||
}
|
||||
|
||||
char *
|
||||
@@ -215,29 +201,16 @@ Tcl_WinTCharToUtf(
|
||||
int len,
|
||||
Tcl_DString *dsPtr)
|
||||
{
|
||||
#if TCL_UTF_MAX > 3
|
||||
char *p;
|
||||
int size;
|
||||
|
||||
if (len > 0) {
|
||||
Tcl_DStringInit(dsPtr);
|
||||
if (!string) {
|
||||
return NULL;
|
||||
}
|
||||
if (len < 0) {
|
||||
len = wcslen((wchar_t *)string);
|
||||
} else {
|
||||
len /= 2;
|
||||
}
|
||||
size = WideCharToMultiByte(CP_UTF8, 0, string, len, 0, 0, NULL, NULL);
|
||||
Tcl_DStringInit(dsPtr);
|
||||
Tcl_DStringSetLength(dsPtr, size+1);
|
||||
p = (char *)Tcl_DStringValue(dsPtr);
|
||||
WideCharToMultiByte(CP_UTF8, 0, string, len, p, size, NULL, NULL);
|
||||
if (len == -1) --size; /* account for 0-byte at string end */
|
||||
Tcl_DStringSetLength(dsPtr, size);
|
||||
p[size] = 0;
|
||||
return p;
|
||||
#else
|
||||
if (!winTCharEncoding) {
|
||||
winTCharEncoding = Tcl_GetEncoding(0, "unicode");
|
||||
}
|
||||
return Tcl_ExternalToUtfDString(winTCharEncoding,
|
||||
string, len, dsPtr);
|
||||
#endif
|
||||
return Tcl_UniCharToUtfDString((Tcl_UniChar *)string, len, dsPtr);
|
||||
}
|
||||
|
||||
#if defined(TCL_WIDE_INT_IS_LONG)
|
||||
|
||||
+15
-5
@@ -243,7 +243,7 @@ Tcl_UniCharToUtfDString(
|
||||
*/
|
||||
|
||||
oldLength = Tcl_DStringLength(dsPtr);
|
||||
Tcl_DStringSetLength(dsPtr, (oldLength + uniLength + 1) * TCL_UTF_MAX);
|
||||
Tcl_DStringSetLength(dsPtr, oldLength + (uniLength + 1) * TCL_UTF_MAX);
|
||||
string = Tcl_DStringValue(dsPtr) + oldLength;
|
||||
|
||||
p = string;
|
||||
@@ -432,17 +432,27 @@ Tcl_UtfToUniCharDString(
|
||||
*/
|
||||
|
||||
oldLength = Tcl_DStringLength(dsPtr);
|
||||
/* TODO: fix overreach! */
|
||||
|
||||
Tcl_DStringSetLength(dsPtr,
|
||||
(int) ((oldLength + length + 1) * sizeof(Tcl_UniChar)));
|
||||
oldLength + (int) ((length + 1) * sizeof(Tcl_UniChar)));
|
||||
wString = (Tcl_UniChar *) (Tcl_DStringValue(dsPtr) + oldLength);
|
||||
|
||||
w = wString;
|
||||
end = src + length;
|
||||
for (p = src; p < end; ) {
|
||||
p = src;
|
||||
end = src + length - TCL_UTF_MAX;
|
||||
while (p < end) {
|
||||
p += TclUtfToUniChar(p, &ch);
|
||||
*w++ = ch;
|
||||
}
|
||||
end += TCL_UTF_MAX;
|
||||
while (p < end) {
|
||||
if (Tcl_UtfCharComplete(p, end-p)) {
|
||||
p += TclUtfToUniChar(p, &ch);
|
||||
} else {
|
||||
ch = UCHAR(*p++);
|
||||
}
|
||||
*w++ = ch;
|
||||
}
|
||||
*w = '\0';
|
||||
Tcl_DStringSetLength(dsPtr,
|
||||
(oldLength + ((char *) w - (char *) wString)));
|
||||
|
||||
+30
-23
@@ -972,20 +972,24 @@ TclLookupSimpleVar(
|
||||
}
|
||||
}
|
||||
} else { /* Local var: look in frame varFramePtr. */
|
||||
int localLen, localCt = varFramePtr->numCompiledLocals;
|
||||
Tcl_Obj **objPtrPtr = &varFramePtr->localCachePtr->varName0;
|
||||
const char *localNameStr;
|
||||
int localCt = varFramePtr->numCompiledLocals;
|
||||
|
||||
for (i=0 ; i<localCt ; i++, objPtrPtr++) {
|
||||
register Tcl_Obj *objPtr = *objPtrPtr;
|
||||
if (localCt > 0) {
|
||||
Tcl_Obj **objPtrPtr = &varFramePtr->localCachePtr->varName0;
|
||||
const char *localNameStr;
|
||||
int localLen;
|
||||
|
||||
if (objPtr) {
|
||||
localNameStr = TclGetStringFromObj(objPtr, &localLen);
|
||||
for (i=0 ; i<localCt ; i++, objPtrPtr++) {
|
||||
register Tcl_Obj *objPtr = *objPtrPtr;
|
||||
|
||||
if ((varLen == localLen) && (varName[0] == localNameStr[0])
|
||||
if (objPtr) {
|
||||
localNameStr = TclGetStringFromObj(objPtr, &localLen);
|
||||
|
||||
if ((varLen == localLen) && (varName[0] == localNameStr[0])
|
||||
&& !memcmp(varName, localNameStr, varLen)) {
|
||||
*indexPtr = i;
|
||||
return (Var *) &varFramePtr->compiledLocals[i];
|
||||
*indexPtr = i;
|
||||
return (Var *) &varFramePtr->compiledLocals[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6132,7 +6136,7 @@ AppendLocals(
|
||||
Interp *iPtr = (Interp *) interp;
|
||||
Var *varPtr;
|
||||
int i, localVarCt, added;
|
||||
Tcl_Obj **varNamePtr, *objNamePtr;
|
||||
Tcl_Obj *objNamePtr;
|
||||
const char *varName;
|
||||
TclVarHashTable *localVarTablePtr;
|
||||
Tcl_HashSearch search;
|
||||
@@ -6142,27 +6146,30 @@ AppendLocals(
|
||||
localVarCt = iPtr->varFramePtr->numCompiledLocals;
|
||||
varPtr = iPtr->varFramePtr->compiledLocals;
|
||||
localVarTablePtr = iPtr->varFramePtr->varTablePtr;
|
||||
varNamePtr = &iPtr->varFramePtr->localCachePtr->varName0;
|
||||
if (includeLinks) {
|
||||
Tcl_InitObjHashTable(&addedTable);
|
||||
}
|
||||
|
||||
for (i = 0; i < localVarCt; i++, varNamePtr++) {
|
||||
/*
|
||||
* Skip nameless (temporary) variables and undefined variables.
|
||||
*/
|
||||
if (localVarCt > 0) {
|
||||
Tcl_Obj **varNamePtr = &iPtr->varFramePtr->localCachePtr->varName0;
|
||||
|
||||
if (*varNamePtr && !TclIsVarUndefined(varPtr)
|
||||
for (i = 0; i < localVarCt; i++, varNamePtr++) {
|
||||
/*
|
||||
* Skip nameless (temporary) variables and undefined variables.
|
||||
*/
|
||||
|
||||
if (*varNamePtr && !TclIsVarUndefined(varPtr)
|
||||
&& (includeLinks || !TclIsVarLink(varPtr))) {
|
||||
varName = TclGetString(*varNamePtr);
|
||||
if ((pattern == NULL) || Tcl_StringMatch(varName, pattern)) {
|
||||
Tcl_ListObjAppendElement(interp, listPtr, *varNamePtr);
|
||||
if (includeLinks) {
|
||||
Tcl_CreateHashEntry(&addedTable, *varNamePtr, &added);
|
||||
varName = TclGetString(*varNamePtr);
|
||||
if ((pattern == NULL) || Tcl_StringMatch(varName, pattern)) {
|
||||
Tcl_ListObjAppendElement(interp, listPtr, *varNamePtr);
|
||||
if (includeLinks) {
|
||||
Tcl_CreateHashEntry(&addedTable, *varNamePtr, &added);
|
||||
}
|
||||
}
|
||||
}
|
||||
varPtr++;
|
||||
}
|
||||
varPtr++;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
+2177
-335
File diff suppressed because it is too large
Load Diff
@@ -1,2 +1,2 @@
|
||||
if {![package vsatisfies [package provide Tcl] 8.6-]} {return}
|
||||
package ifneeded http 2.8.13 [list tclPkgSetup $dir http 2.8.13 {{http.tcl source {::http::config ::http::formatQuery ::http::geturl ::http::reset ::http::wait ::http::register ::http::unregister ::http::mapReply}}}]
|
||||
package ifneeded http 2.9.0 [list tclPkgSetup $dir http 2.9.0 {{http.tcl source {::http::config ::http::formatQuery ::http::geturl ::http::reset ::http::wait ::http::register ::http::unregister ::http::mapReply}}}]
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
if {([info commands ::tcl::pkgconfig] eq "")
|
||||
|| ([info sharedlibextension] ne ".dll")} return
|
||||
if {[::tcl::pkgconfig get debug]} {
|
||||
package ifneeded registry 1.3.2 \
|
||||
package ifneeded registry 1.3.3 \
|
||||
[list load [file join $dir tclreg13g.dll] registry]
|
||||
} else {
|
||||
package ifneeded registry 1.3.2 \
|
||||
package ifneeded registry 1.3.3 \
|
||||
[list load [file join $dir tclreg13.dll] registry]
|
||||
}
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ testConstraint testsetplatform [llength [info commands testsetplatform]]
|
||||
testConstraint testvolumetype [llength [info commands testvolumetype]]
|
||||
testConstraint linkDirectory [expr {
|
||||
![testConstraint win] ||
|
||||
([string index $tcl_platform(osVersion) 0] >= 5
|
||||
($::tcl_platform(osVersion) >= 5.0
|
||||
&& [lindex [file system [temporaryDirectory]] 1] eq "NTFS")
|
||||
}]
|
||||
|
||||
|
||||
+5
-6
@@ -65,11 +65,10 @@ if {[testConstraint unix]} {
|
||||
|
||||
# Also used in winFCmd...
|
||||
if {[testConstraint win]} {
|
||||
set major [string index $tcl_platform(osVersion) 0]
|
||||
if {[testConstraint nt] && $major > 4} {
|
||||
if {$major > 5} {
|
||||
if {[testConstraint nt] && $::tcl_platform(osVersion) >= 5.0} {
|
||||
if {$::tcl_platform(osVersion) >= 6.0} {
|
||||
testConstraint winVista 1
|
||||
} elseif {$major == 5} {
|
||||
} else {
|
||||
testConstraint win2000orXP 1
|
||||
}
|
||||
}
|
||||
@@ -78,7 +77,7 @@ if {[testConstraint win]} {
|
||||
testConstraint darwin9 [expr {
|
||||
[testConstraint unix]
|
||||
&& $tcl_platform(os) eq "Darwin"
|
||||
&& [package vsatisfies 1.$tcl_platform(osVersion) 1.9]
|
||||
&& [package vsatisfies 1.$::tcl_platform(osVersion) 1.9]
|
||||
}]
|
||||
testConstraint notDarwin9 [expr {![testConstraint darwin9]}]
|
||||
|
||||
@@ -2309,7 +2308,7 @@ test fCmd-27.6 {TclFileAttrsCmd - setting more than one option} -setup {
|
||||
|
||||
if {
|
||||
[testConstraint win] &&
|
||||
([string index $tcl_platform(osVersion) 0] < 5
|
||||
($::tcl_platform(osVersion) < 5.0
|
||||
|| [lindex [file system [temporaryDirectory]] 1] ne "NTFS")
|
||||
} then {
|
||||
testConstraint linkDirectory 0
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ testConstraint testtranslatefilename [llength [info commands testtranslatefilena
|
||||
testConstraint linkDirectory 1
|
||||
testConstraint symbolicLinkFile 1
|
||||
if {[testConstraint win]} {
|
||||
if {[string index $tcl_platform(osVersion) 0] < 5 \
|
||||
if {$::tcl_platform(osVersion) < 5.0 \
|
||||
|| [lindex [file system [temporaryDirectory]] 1] ne "NTFS"} {
|
||||
testConstraint linkDirectory 0
|
||||
}
|
||||
|
||||
+3
-3
@@ -86,7 +86,7 @@ if {[catch {package present Thread}] == 0 && [file exists $httpdFile]} {
|
||||
test http-1.1 {http::config} {
|
||||
http::config -useragent UserAgent
|
||||
http::config
|
||||
} [list -accept */* -proxyfilter http::ProxyRequired -proxyhost {} -proxyport {} -urlencoding utf-8 -useragent "UserAgent"]
|
||||
} [list -accept */* -pipeline 1 -postfresh 0 -proxyfilter http::ProxyRequired -proxyhost {} -proxyport {} -repost 0 -urlencoding utf-8 -useragent UserAgent -zip 1]
|
||||
test http-1.2 {http::config} {
|
||||
http::config -proxyfilter
|
||||
} http::ProxyRequired
|
||||
@@ -101,10 +101,10 @@ test http-1.4 {http::config} {
|
||||
set x [http::config]
|
||||
http::config {*}$savedconf
|
||||
set x
|
||||
} {-accept */* -proxyfilter myFilter -proxyhost nowhere.come -proxyport 8080 -urlencoding iso8859-1 -useragent {Tcl Test Suite}}
|
||||
} {-accept */* -pipeline 1 -postfresh 0 -proxyfilter myFilter -proxyhost nowhere.come -proxyport 8080 -repost 0 -urlencoding iso8859-1 -useragent {Tcl Test Suite} -zip 1}
|
||||
test http-1.5 {http::config} -returnCodes error -body {
|
||||
http::config -proxyhost {} -junk 8080
|
||||
} -result {Unknown option -junk, must be: -accept, -proxyfilter, -proxyhost, -proxyport, -urlencoding, -useragent}
|
||||
} -result {Unknown option -junk, must be: -accept, -pipeline, -postfresh, -proxyfilter, -proxyhost, -proxyport, -repost, -urlencoding, -useragent, -zip}
|
||||
test http-1.6 {http::config} -setup {
|
||||
set oldenc [http::config -urlencoding]
|
||||
} -body {
|
||||
|
||||
+1
-4
@@ -515,10 +515,7 @@ proc handler {var sock token} {
|
||||
set chunk [read $sock]
|
||||
append data $chunk
|
||||
#::http::Log "handler read [string length $chunk] ([chan configure $sock -buffersize])"
|
||||
if {[eof $sock]} {
|
||||
#::http::Log "handler eof $sock"
|
||||
chan event $sock readable {}
|
||||
}
|
||||
return [string length $chunk]
|
||||
}
|
||||
|
||||
test http11-3.0 "-handler,close,identity" -setup {
|
||||
|
||||
@@ -0,0 +1,866 @@
|
||||
# httpPipeline.test
|
||||
#
|
||||
# Test HTTP/1.1 concurrent requests including
|
||||
# queueing, pipelining and retries.
|
||||
#
|
||||
# Copyright (C) 2018 Keith Nash <kjnash@users.sourceforge.net>
|
||||
#
|
||||
# See the file "license.terms" for information on usage and redistribution
|
||||
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
||||
|
||||
package require tcltest 2
|
||||
namespace import -force ::tcltest::*
|
||||
|
||||
package require http 2.8
|
||||
|
||||
set sourcedir [file normalize [file dirname [info script]]]
|
||||
source [file join $sourcedir httpTest.tcl]
|
||||
source [file join $sourcedir httpTestScript.tcl]
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# (1) Define the test scripts that will be used to generate logs for analysis -
|
||||
# and also define the "correct" results.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc ReturnTestScriptAndResult {ca cb delay te} {
|
||||
|
||||
switch -- $ca {
|
||||
1 {set start {
|
||||
START
|
||||
KEEPALIVE 0
|
||||
PIPELINE 0
|
||||
}}
|
||||
|
||||
2 {set start {
|
||||
START
|
||||
KEEPALIVE 0
|
||||
PIPELINE 1
|
||||
}}
|
||||
|
||||
3 {set start {
|
||||
START
|
||||
KEEPALIVE 1
|
||||
PIPELINE 0
|
||||
}}
|
||||
|
||||
4 {set start {
|
||||
START
|
||||
KEEPALIVE 1
|
||||
PIPELINE 1
|
||||
}}
|
||||
|
||||
default {
|
||||
return -code error {no matching script}
|
||||
}
|
||||
}
|
||||
|
||||
set middle "
|
||||
[list DELAY $delay]
|
||||
"
|
||||
|
||||
switch -- $cb {
|
||||
1 {set end {
|
||||
GET a
|
||||
GET b
|
||||
GET c
|
||||
GET a
|
||||
STOP
|
||||
}
|
||||
set resShort {1 ? ? ?}
|
||||
set resLong {1 2 3 4}
|
||||
}
|
||||
|
||||
2 {set end {
|
||||
GET a
|
||||
HEAD b
|
||||
GET c
|
||||
HEAD a
|
||||
HEAD c
|
||||
STOP
|
||||
}
|
||||
set resShort {1 ? ? ? ?}
|
||||
set resLong {1 2 3 4 5}
|
||||
}
|
||||
|
||||
3 {set end {
|
||||
HEAD a
|
||||
GET b
|
||||
HEAD c
|
||||
HEAD b
|
||||
GET a
|
||||
GET b
|
||||
STOP
|
||||
}
|
||||
set resShort {1 ? ? ? ? ?}
|
||||
set resLong {1 2 3 4 5 6}
|
||||
}
|
||||
|
||||
4 {set end {
|
||||
GET a
|
||||
GET b
|
||||
GET c
|
||||
GET a
|
||||
POST b address=home code=brief paid=yes
|
||||
GET c
|
||||
GET a
|
||||
GET b
|
||||
GET c
|
||||
STOP
|
||||
}
|
||||
set resShort {1 ? ? ? 5 ? ? ? ?}
|
||||
set resLong {1 2 3 4 5 6 7 8 9}
|
||||
}
|
||||
|
||||
5 {set end {
|
||||
POST a address=home code=brief paid=yes
|
||||
POST b address=home code=brief paid=yes
|
||||
POST c address=home code=brief paid=yes
|
||||
POST a address=home code=brief paid=yes
|
||||
POST b address=home code=brief paid=yes
|
||||
POST c address=home code=brief paid=yes
|
||||
POST a address=home code=brief paid=yes
|
||||
POST b address=home code=brief paid=yes
|
||||
POST c address=home code=brief paid=yes
|
||||
STOP
|
||||
}
|
||||
set resShort {1 2 3 4 5 6 7 8 9}
|
||||
set resLong {1 2 3 4 5 6 7 8 9}
|
||||
}
|
||||
|
||||
6 {set end {
|
||||
POST a address=home code=brief paid=yes
|
||||
GET b address=home code=brief paid=yes
|
||||
POST c address=home code=brief paid=yes
|
||||
GET a address=home code=brief paid=yes
|
||||
GET b address=home code=brief paid=yes
|
||||
POST c address=home code=brief paid=yes
|
||||
POST a address=home code=brief paid=yes
|
||||
HEAD b address=home code=brief paid=yes
|
||||
GET c address=home code=brief paid=yes
|
||||
STOP
|
||||
}
|
||||
set resShort {1 ? 3 ? ? 6 7 ? ?}
|
||||
set resLong {1 2 3 4 5 6 7 8 9}
|
||||
}
|
||||
|
||||
7 {set end {
|
||||
GET b address=home code=brief paid=yes
|
||||
POST a address=home code=brief paid=yes
|
||||
GET a address=home code=brief paid=yes
|
||||
POST c address=home code=brief paid=yes
|
||||
GET b address=home code=brief paid=yes
|
||||
HEAD b address=home code=brief paid=yes
|
||||
POST c address=home code=brief paid=yes
|
||||
POST a address=home code=brief paid=yes
|
||||
GET c address=home code=brief paid=yes
|
||||
STOP
|
||||
}
|
||||
set resShort {1 2 ? 4 ? ? 7 8 ?}
|
||||
set resLong {1 2 3 4 5 6 7 8 9}
|
||||
}
|
||||
|
||||
8 {set end {
|
||||
# Telling the server to close the connection.
|
||||
GET a
|
||||
GET b close=y
|
||||
GET c
|
||||
GET a
|
||||
GET b
|
||||
GET c
|
||||
GET a
|
||||
GET b
|
||||
GET c
|
||||
STOP
|
||||
}
|
||||
set resShort {1 ? 3 ? ? ? ? ? ?}
|
||||
set resLong {1 2 3 4 5 6 7 8 9}
|
||||
}
|
||||
|
||||
9 {set end {
|
||||
# Telling the server to close the connection.
|
||||
GET a
|
||||
POST b close=y address=home code=brief paid=yes
|
||||
GET c
|
||||
GET a
|
||||
GET b
|
||||
GET c
|
||||
GET a
|
||||
GET b
|
||||
GET c
|
||||
STOP
|
||||
}
|
||||
set resShort {1 2 3 ? ? ? ? ? ?}
|
||||
set resLong {1 2 3 4 5 6 7 8 9}
|
||||
}
|
||||
|
||||
10 {set end {
|
||||
# Telling the server to close the connection.
|
||||
GET a
|
||||
GET b close=y
|
||||
POST c address=home code=brief paid=yes
|
||||
GET a
|
||||
GET b
|
||||
GET c
|
||||
GET a
|
||||
GET b
|
||||
GET c
|
||||
STOP
|
||||
}
|
||||
set resShort {1 ? 3 ? ? ? ? ? ?}
|
||||
set resLong {1 2 3 4 5 6 7 8 9}
|
||||
}
|
||||
|
||||
11 {set end {
|
||||
# Telling the server to close the connection twice.
|
||||
GET a
|
||||
GET b close=y
|
||||
GET c
|
||||
GET a
|
||||
GET b close=y
|
||||
GET c
|
||||
GET a
|
||||
GET b
|
||||
GET c
|
||||
STOP
|
||||
}
|
||||
set resShort {1 ? 3 ? ? 6 ? ? ?}
|
||||
set resLong {1 2 3 4 5 6 7 8 9}
|
||||
}
|
||||
|
||||
12 {set end {
|
||||
# Telling the server to delay before sending the response.
|
||||
GET a
|
||||
GET b delay=1
|
||||
GET c
|
||||
GET a
|
||||
GET b
|
||||
STOP
|
||||
}
|
||||
set resShort {1 ? ? ? ?}
|
||||
set resLong {1 2 3 4 5}
|
||||
}
|
||||
|
||||
13 {set end {
|
||||
# Making the server close the connection (time out).
|
||||
GET a
|
||||
WAIT 2000
|
||||
GET b
|
||||
GET c
|
||||
GET a
|
||||
GET b
|
||||
STOP
|
||||
}
|
||||
set resShort {1 2 ? ? ?}
|
||||
set resLong {1 2 3 4 5}
|
||||
}
|
||||
|
||||
14 {set end {
|
||||
# Making the server close the connection (time out) twice.
|
||||
GET a
|
||||
WAIT 2000
|
||||
GET b
|
||||
GET c
|
||||
GET a
|
||||
WAIT 2000
|
||||
GET b
|
||||
GET c
|
||||
GET a
|
||||
GET b
|
||||
GET c
|
||||
STOP
|
||||
}
|
||||
set resShort {1 2 ? ? 5 ? ? ? ?}
|
||||
set resLong {1 2 3 4 5 6 7 8 9}
|
||||
}
|
||||
|
||||
15 {set end {
|
||||
POST a address=home code=brief paid=yes
|
||||
POST b address=home code=brief paid=yes close=y delay=1
|
||||
POST c address=home code=brief paid=yes delay=1
|
||||
POST a address=home code=brief paid=yes close=y
|
||||
WAIT 2000
|
||||
POST b address=home code=brief paid=yes delay=1
|
||||
POST c address=home code=brief paid=yes close=y
|
||||
POST a address=home code=brief paid=yes
|
||||
POST b address=home code=brief paid=yes close=y
|
||||
POST c address=home code=brief paid=yes
|
||||
STOP
|
||||
}
|
||||
set resShort {1 2 3 4 5 6 7 8 9}
|
||||
set resLong {1 2 3 4 5 6 7 8 9}
|
||||
}
|
||||
|
||||
16 {set end {
|
||||
POST a address=home code=brief paid=yes
|
||||
GET b address=home code=brief paid=yes
|
||||
POST c address=home code=brief paid=yes close=y
|
||||
GET a address=home code=brief paid=yes
|
||||
GET b address=home code=brief paid=yes close=y
|
||||
POST c address=home code=brief paid=yes
|
||||
WAIT 2000
|
||||
POST a address=home code=brief paid=yes
|
||||
HEAD b address=home code=brief paid=yes close=y
|
||||
GET c address=home code=brief paid=yes
|
||||
STOP
|
||||
}
|
||||
set resShort {1 ? 3 4 ? 6 7 ? 9}
|
||||
set resLong {1 2 3 4 5 6 7 8 9}
|
||||
}
|
||||
|
||||
17 {set end {
|
||||
GET b address=home code=brief paid=yes
|
||||
POST a address=home code=brief paid=yes
|
||||
GET a address=home code=brief paid=yes
|
||||
POST c address=home code=brief paid=yes close=y
|
||||
GET b address=home code=brief paid=yes
|
||||
HEAD b address=home code=brief paid=yes close=y
|
||||
POST c address=home code=brief paid=yes
|
||||
WAIT 2000
|
||||
POST a address=home code=brief paid=yes
|
||||
WAIT 2000
|
||||
GET c address=home code=brief paid=yes
|
||||
STOP
|
||||
}
|
||||
set resShort {1 2 3 4 5 ? 7 8 9}
|
||||
set resLong {1 2 3 4 5 6 7 8 9}
|
||||
}
|
||||
|
||||
|
||||
18 {set end {
|
||||
REPOST 0
|
||||
GET a
|
||||
WAIT 2000
|
||||
POST b address=home code=brief paid=yes
|
||||
GET c
|
||||
GET a
|
||||
STOP
|
||||
}
|
||||
set resShort {1 2 ? ?}
|
||||
set resLong {1 2 3 4}
|
||||
# resShort is overwritten below for the case ($te == 1).
|
||||
}
|
||||
|
||||
|
||||
19 {set end {
|
||||
REPOST 0
|
||||
GET a
|
||||
WAIT 2000
|
||||
GET b address=home code=brief paid=yes
|
||||
GET c
|
||||
GET a
|
||||
STOP
|
||||
}
|
||||
set resShort {1 2 ? ?}
|
||||
set resLong {1 2 3 4}
|
||||
}
|
||||
|
||||
|
||||
20 {set end {
|
||||
POSTFRESH 1
|
||||
GET a
|
||||
WAIT 2000
|
||||
POST b address=home code=brief paid=yes
|
||||
GET c
|
||||
GET a
|
||||
STOP
|
||||
}
|
||||
set resShort {1 3 ?}
|
||||
set resLong {1 3 4}
|
||||
}
|
||||
|
||||
|
||||
21 {set end {
|
||||
POSTFRESH 1
|
||||
GET a
|
||||
WAIT 2000
|
||||
GET b address=home code=brief paid=yes
|
||||
GET c
|
||||
GET a
|
||||
STOP
|
||||
}
|
||||
set resShort {1 2 ? ?}
|
||||
set resLong {1 2 3 4}
|
||||
}
|
||||
|
||||
22 {set end {
|
||||
GET a
|
||||
WAIT 2000
|
||||
KEEPALIVE 0
|
||||
POST b address=home code=brief paid=yes
|
||||
KEEPALIVE 1
|
||||
GET c
|
||||
GET a
|
||||
STOP
|
||||
}
|
||||
set resShort {1 3 ?}
|
||||
set resLong {1 3 4}
|
||||
}
|
||||
|
||||
|
||||
23 {set end {
|
||||
GET a
|
||||
WAIT 2000
|
||||
KEEPALIVE 0
|
||||
GET b address=home code=brief paid=yes
|
||||
KEEPALIVE 1
|
||||
GET c
|
||||
GET a
|
||||
STOP
|
||||
}
|
||||
set resShort {1 3 ?}
|
||||
set resLong {1 3 4}
|
||||
}
|
||||
|
||||
24 {set end {
|
||||
GET a
|
||||
KEEPALIVE 0
|
||||
POST b address=home code=brief paid=yes
|
||||
KEEPALIVE 1
|
||||
GET c
|
||||
GET a
|
||||
STOP
|
||||
}
|
||||
set resShort {1 ? ?}
|
||||
set resLong {1 3 4}
|
||||
}
|
||||
|
||||
|
||||
25 {set end {
|
||||
GET a
|
||||
KEEPALIVE 0
|
||||
GET b address=home code=brief paid=yes
|
||||
KEEPALIVE 1
|
||||
GET c
|
||||
GET a
|
||||
STOP
|
||||
}
|
||||
set resShort {1 ? ?}
|
||||
set resLong {1 3 4}
|
||||
}
|
||||
|
||||
default {
|
||||
return -code error {no matching script}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if {$ca < 3} {
|
||||
# Not Keep-Alive.
|
||||
set result "Passed all sanity checks."
|
||||
|
||||
} elseif {$ca == 3} {
|
||||
# Keep-Alive, not pipelined.
|
||||
set result {}
|
||||
append result "Passed all sanity checks.\n"
|
||||
append result "Have overlaps including response body:\n"
|
||||
|
||||
} else {
|
||||
# Keep-Alive, pipelined: ($ca == 4)
|
||||
set result {}
|
||||
append result "Passed all sanity checks.\n"
|
||||
append result "Overlap-free without response body:\n"
|
||||
append result "$resShort"
|
||||
}
|
||||
|
||||
# - The special case of test *.18*-testEof needs test results to be
|
||||
# individually written.
|
||||
# - These test -repost 0 when there is a POST to apply it to, and the server
|
||||
# timeout has not been detected.
|
||||
if {($cb == 18) && ($te == 1)} {
|
||||
if {$ca < 3} {
|
||||
# Not Keep-Alive.
|
||||
set result "Passed all sanity checks."
|
||||
|
||||
} elseif {$ca == 3 && $delay == 0} {
|
||||
# Keep-Alive, not pipelined.
|
||||
set result [MakeMessage {
|
||||
|Problems with sanity checks:
|
||||
|Wrong sequence for token ::http::2 - {A B C D X X X}
|
||||
|- and error(s) X
|
||||
|Wrong sequence for token ::http::3 - {A X X}
|
||||
|- and error(s) X
|
||||
|Wrong sequence for token ::http::4 - {A X X X}
|
||||
|- and error(s) X
|
||||
|
|
||||
|Have overlaps including response body:
|
||||
|
|
||||
}]
|
||||
|
||||
} elseif {$ca == 3} {
|
||||
# Keep-Alive, not pipelined.
|
||||
set result [MakeMessage {
|
||||
|Problems with sanity checks:
|
||||
|Wrong sequence for token ::http::2 - {A B C D X X X}
|
||||
|- and error(s) X
|
||||
|
|
||||
|Have overlaps including response body:
|
||||
|
|
||||
}]
|
||||
|
||||
} elseif {$delay == 0} {
|
||||
# Keep-Alive, pipelined: ($ca == 4)
|
||||
set result [MakeMessage {
|
||||
|Problems with sanity checks:
|
||||
|Wrong sequence for token ::http::2 - {A B C D X X X}
|
||||
|- and error(s) X
|
||||
|Wrong sequence for token ::http::3 - {A X X}
|
||||
|- and error(s) X
|
||||
|Wrong sequence for token ::http::4 - {A X X X}
|
||||
|- and error(s) X
|
||||
|
|
||||
|Overlap-free without response body:
|
||||
|
|
||||
}]
|
||||
|
||||
} else {
|
||||
set result [MakeMessage {
|
||||
|Problems with sanity checks:
|
||||
|Wrong sequence for token ::http::2 - {A B C D X X X}
|
||||
|- and error(s) X
|
||||
|
|
||||
|Overlap-free without response body:
|
||||
|
|
||||
}]
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return [list "$start$middle$end" $result]
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Proc MakeMessage
|
||||
# ------------------------------------------------------------------------------
|
||||
# WHD's one-line command to generate multi-line strings from readable code.
|
||||
#
|
||||
# Example:
|
||||
# set blurb [MakeMessage {
|
||||
# |This command allows multi-line strings to be created with readable
|
||||
# |code, and without breaking the rules for indentation.
|
||||
# |
|
||||
# |The command shifts the entire block of text to the left, omitting
|
||||
# |the pipe character and the spaces to its left.
|
||||
# }]
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc MakeMessage {in} {
|
||||
regsub -all -line {^\s*\|} [string trim $in] {}
|
||||
# N.B. Implicit Return.
|
||||
}
|
||||
|
||||
|
||||
proc ReturnTestScript {ca cb delay te} {
|
||||
lassign [ReturnTestScriptAndResult $ca $cb $delay $te] script result
|
||||
return $script
|
||||
}
|
||||
|
||||
proc ReturnTestResult {ca cb delay te} {
|
||||
lassign [ReturnTestScriptAndResult $ca $cb $delay $te] script result
|
||||
return $result
|
||||
}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# (2) Command to run a test script and use httpTest to analyse the logs.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
namespace import httpTestScript::runHttpTestScript
|
||||
namespace import httpTestScript::cleanupHttpTestScript
|
||||
namespace import httpTest::cleanupHttpTest
|
||||
namespace import httpTest::logAnalyse
|
||||
namespace import httpTest::setHttpTestOptions
|
||||
|
||||
proc RunTest {header footer delay te} {
|
||||
set num [runHttpTestScript [ReturnTestScript $header $footer $delay $te]]
|
||||
set skipOverlaps 0
|
||||
set notPiped {}
|
||||
set notIncluded {}
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Custom code for specific tests
|
||||
# --------------------------------------------------------------------------
|
||||
if {$header < 3} {
|
||||
set skipOverlaps 1
|
||||
for {set i 1} {$i <= $num} {incr i} {
|
||||
lappend notPiped $i
|
||||
}
|
||||
} elseif {$header > 2 && $footer == 18 && $te == 1} {
|
||||
set skipOverlaps 1
|
||||
if {$delay == 0} {
|
||||
# Transaction 1 is conventional.
|
||||
# Check that transactions 2,3,4 are cancelled.
|
||||
set notPiped {1}
|
||||
set notIncluded $notPiped
|
||||
} else {
|
||||
# Transaction 1 is conventional.
|
||||
# Check that transaction 2 is cancelled.
|
||||
# The timing of transactions 3 and 4 is uncertain.
|
||||
set notPiped {1 3 4}
|
||||
set notIncluded $notPiped
|
||||
}
|
||||
} elseif {$footer in {20 22 23 24 25}} {
|
||||
# Transaction 2 uses its own socket.
|
||||
set notPiped 2
|
||||
set notIncluded $notPiped
|
||||
} else {
|
||||
}
|
||||
# --------------------------------------------------------------------------
|
||||
# End of custom code for specific tests
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
|
||||
set Results [logAnalyse $num $skipOverlaps $notIncluded $notPiped]
|
||||
lassign $Results msg cleanE cleanF dirtyE dirtyF
|
||||
if {$msg eq {}} {
|
||||
set msg "Passed all sanity checks."
|
||||
} else {
|
||||
set msg "Problems with sanity checks:\n$msg"
|
||||
}
|
||||
|
||||
if 0 {
|
||||
puts $msg
|
||||
puts "Overlap-free including response body:\n$cleanF"
|
||||
puts "Have overlaps including response body:\n$dirtyF"
|
||||
puts "Overlap-free without response body:\n$cleanE"
|
||||
puts "Have overlaps without response body:\n$dirtyE"
|
||||
}
|
||||
|
||||
if {$header < 3} {
|
||||
# No ordering, just check that transactions all finish
|
||||
set result $msg
|
||||
} elseif {$header == 3} {
|
||||
# Not pipelined - check overlaps with response body.
|
||||
set result "$msg\nHave overlaps including response body:\n$dirtyF"
|
||||
} else {
|
||||
# Pipelined - check overlaps without response body. Check that the
|
||||
# first request, the first requests after replay, and POSTs are clean.
|
||||
set result "$msg\nOverlap-free without response body:\n$cleanE"
|
||||
}
|
||||
set ::nTokens $num
|
||||
return $result
|
||||
}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# (3) VERBOSITY CONTROL
|
||||
# ------------------------------------------------------------------------------
|
||||
# If tests fail, run an individual test with -verbose 1 or 2 for diagnosis.
|
||||
# If still obscure, uncomment #Log and ##Log lines in the http package.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
setHttpTestOptions -verbose 0
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# (4) Define the base URLs used for testing. Each must have a query string.
|
||||
# ------------------------------------------------------------------------------
|
||||
# - A HTTP/1.1 server is required. It should be configured to provide
|
||||
# persistent connections when requested to do so, and to close these
|
||||
# connections if they are idle for one second.
|
||||
# - The resource must be served with status 200 in response to a valid GET or
|
||||
# POST.
|
||||
# - The value of "page" is always specified in the query-string. Different
|
||||
# resources for the three values of "page" allow testing of both chunked and
|
||||
# unchunked transfer encoding.
|
||||
# - The variables "close" and "delay" may be specified in the query-string (for
|
||||
# a GET) or the request body (for a POST).
|
||||
# - "delay" is a numerical value in seconds, and causes the server to delay
|
||||
# the response, including headers.
|
||||
# - "close", if it has the value "y", instructs the server to close the
|
||||
# connection ater the current request.
|
||||
# - Any other variables should be ignored.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
namespace eval ::httpTestScript {
|
||||
variable URL
|
||||
array set URL {
|
||||
a http://test-tcl-http.kerlin.org/index.html?page=privacy
|
||||
b http://test-tcl-http.kerlin.org/index.html?page=conditions
|
||||
c http://test-tcl-http.kerlin.org/index.html?page=welcome
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# (5) Define the tests
|
||||
# ------------------------------------------------------------------------------
|
||||
# Constraints:
|
||||
# - serverNeeded - the URLs defined at (4) must be available, and must have the
|
||||
# properties specified there.
|
||||
# - duplicate - the value of -pipeline does not matter if -keepalive 0
|
||||
# - timeout1s - tests that work correctly only if the server closes
|
||||
# persistent connections after one second.
|
||||
#
|
||||
# Server timeout of persistent connections should be 1s. Delays of 2s are
|
||||
# intended to cause timeout.
|
||||
# Servers are usually configured to use a longer timeout: this will cause the
|
||||
# tests to fail. The "2000" could be replaced with a larger number, but the
|
||||
# tests will then be inconveniently slow.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
#testConstraint serverNeeded 1
|
||||
#testConstraint timeout1s 1
|
||||
#testConstraint duplicate 1
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Proc SetTestEof - to edit the command ::http::KeepSocket
|
||||
# ------------------------------------------------------------------------------
|
||||
# The usual line in command ::http::KeepSocket is " set TEST_EOF 0".
|
||||
# Whether the value set in the file is 0 or 1, change it here to the value
|
||||
# specified by the argument.
|
||||
#
|
||||
# It is worth doing all tests for both values of the argument.
|
||||
#
|
||||
# test 0 - ::http::KeepSocket is unchanged, detects server eof where possible
|
||||
# and closes the connection.
|
||||
# test 1 - ::http::KeepSocket is edited, does not detect server eof, so the
|
||||
# reaction to finding server eof can be tested without the difficulty
|
||||
# of testing in the few milliseconds of an asynchronous close event.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc SetTestEof {test} {
|
||||
set body [info body ::http::KeepSocket]
|
||||
set subs " set TEST_EOF $test"
|
||||
set count [regsub -line -all -- {^\s*set TEST_EOF .*$} $body $subs newBody]
|
||||
if {$count != 1} {
|
||||
return -code error {proc ::http::KeepSocket has unexpected form}
|
||||
}
|
||||
proc ::http::KeepSocket {token} $newBody
|
||||
return
|
||||
}
|
||||
|
||||
for {set header 1} {$header <= 4} {incr header} {
|
||||
if {$header == 4} {
|
||||
setHttpTestOptions -dotted 1
|
||||
set match glob
|
||||
} else {
|
||||
setHttpTestOptions -dotted 0
|
||||
set match exact
|
||||
}
|
||||
|
||||
if {$header == 2} {
|
||||
set cons0 {serverNeeded duplicate}
|
||||
} else {
|
||||
set cons0 serverNeeded
|
||||
}
|
||||
|
||||
for {set footer 1} {$footer <= 25} {incr footer} {
|
||||
foreach {delay label} {
|
||||
0 a
|
||||
1 b
|
||||
2 c
|
||||
3 d
|
||||
5 e
|
||||
8 f
|
||||
12 g
|
||||
100 h
|
||||
500 i
|
||||
2000 j
|
||||
} {
|
||||
foreach te {0 1} {
|
||||
if {$te} {
|
||||
set tag testEof
|
||||
} else {
|
||||
set tag normal
|
||||
}
|
||||
set suffix {}
|
||||
set cons $cons0
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Custom code for individual tests
|
||||
# ------------------------------------------------------------------
|
||||
if {$footer in {18}} {
|
||||
# Custom code:
|
||||
if {($label eq "j") && ($te == 1)} {
|
||||
continue
|
||||
}
|
||||
if {$te == 1} {
|
||||
# The test (of REPOST 0) is useful if tag is "testEof"
|
||||
# (server timeout without client reaction). The same test
|
||||
# has a different result if tag is "normal".
|
||||
|
||||
set suffix " - extra test for -repost 0 - ::http::2 must be"
|
||||
append suffix " cancelled"
|
||||
if {($delay == 0)} {
|
||||
append suffix ", along with ::http::3 ::http::4 if"
|
||||
append suffix " the test creates these before ::http::2"
|
||||
append suffix " is cancelled"
|
||||
}
|
||||
} else {
|
||||
}
|
||||
} elseif {$footer in {19}} {
|
||||
set suffix " - extra test for -repost 0"
|
||||
} elseif {$footer in {20 21}} {
|
||||
set suffix " - extra test for -postfresh 1"
|
||||
if {($footer == 20)} {
|
||||
append suffix " - ::http::2 uses a separate socket"
|
||||
append suffix ", other requests use a persistent connection"
|
||||
}
|
||||
} elseif {$footer in {22 23 24 25}} {
|
||||
append suffix " - ::http::2 uses a separate socket"
|
||||
append suffix ", other requests use a persistent connection"
|
||||
} else {
|
||||
}
|
||||
|
||||
if {($footer >= 13 && $footer <= 23)} {
|
||||
# Test use WAIT and depend on server timeout before this time.
|
||||
lappend cons timeout1s
|
||||
}
|
||||
# ------------------------------------------------------------------
|
||||
# End of custom code.
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
set name "pipeline test header $header footer $footer delay $delay $tag$suffix"
|
||||
|
||||
|
||||
# Here's the test:
|
||||
test httpPipeline-${header}.${footer}${label}-${tag} $name \
|
||||
-constraints $cons \
|
||||
-setup [string map [list TE $te] {
|
||||
# Restore default values for tests:
|
||||
http::config -pipeline 1 -postfresh 0 -repost 1
|
||||
http::init
|
||||
set http::http(uid) 0
|
||||
SetTestEof {TE}
|
||||
}] -body [list RunTest $header $footer $delay $te] -cleanup {
|
||||
# Restore default values for tests:
|
||||
http::config -pipeline 1 -postfresh 0 -repost 1
|
||||
cleanupHttpTestScript
|
||||
SetTestEof 0
|
||||
cleanupHttpTest
|
||||
after 2000
|
||||
# Wait for persistent sockets on the server to time out.
|
||||
} -result [ReturnTestResult $header $footer $delay $te] -match $match
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# (*) Notes on tests *.18*-testEof, *.19*-testEof - these test -repost 0
|
||||
# ------------------------------------------------------------------------------
|
||||
# These tests are a bit awkward because the main test kit analyses whether all
|
||||
# requests are satisfied, with retries if necessary, and it has result analysis
|
||||
# for processing retry logs.
|
||||
# - *.18*-testEof tests that certain requests are NOT satisfied, so the analysis
|
||||
# is a one-off.
|
||||
# - Tests *.18a-testEof depend on client/server timing - the test needs to call
|
||||
# http::geturl for all requests before the POST (request 2) is cancelled.
|
||||
# We test that requests 2, 3, 4 are all cancelled.
|
||||
# - Other tests *.18*-testEof may not request 3 and 4 in time for the to be
|
||||
# added to the write queue before request 2 is completed. We simply check that
|
||||
# request 2 is cancelled.
|
||||
# - The behaviour is different if all connections are allowed to time out
|
||||
# (label "j"). This case is not needed to test -repost 0, and is omitted.
|
||||
# - Tests *.18*-normal and *.19* are conventional (-repost 0 should have no
|
||||
# effect).
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
|
||||
unset header footer delay label suffix match cons name te
|
||||
namespace delete ::httpTest
|
||||
namespace delete ::httpTestScript
|
||||
|
||||
::tcltest::cleanupTests
|
||||
@@ -0,0 +1,505 @@
|
||||
# httpTest.tcl
|
||||
#
|
||||
# Test HTTP/1.1 concurrent requests including
|
||||
# queueing, pipelining and retries.
|
||||
#
|
||||
# Copyright (C) 2018 Keith Nash <kjnash@users.sourceforge.net>
|
||||
#
|
||||
# See the file "license.terms" for information on usage and redistribution
|
||||
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# "Package" httpTest for analysis of Log output of http requests.
|
||||
# ------------------------------------------------------------------------------
|
||||
# This is a specialised test kit for examining the presence, ordering, and
|
||||
# overlap of multiple HTTP transactions over a persistent ("Keep-Alive")
|
||||
# connection; and also for testing reconnection in accordance with RFC 7230 when
|
||||
# the connection is lost.
|
||||
#
|
||||
# This kit is probably not useful for other purposes. It depends on the
|
||||
# presence of specific Log commands in the http library, and it interprets the
|
||||
# logs that these commands create.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
package require http
|
||||
|
||||
namespace eval ::http {
|
||||
variable TestStartTimeInMs [clock milliseconds]
|
||||
# catch {puts stdout "Start time (zero ms) is $TestStartTimeInMs"}
|
||||
}
|
||||
|
||||
namespace eval ::httpTest {
|
||||
variable testResults {}
|
||||
variable testOptions
|
||||
array set testOptions {
|
||||
-verbose 0
|
||||
-dotted 1
|
||||
}
|
||||
# -verbose - 0 quiet 1 write to stdout 2 write more
|
||||
# -dotted - (boolean) use dots for absences in lists of transactions
|
||||
}
|
||||
|
||||
proc httpTest::Puts {txt} {
|
||||
variable testOptions
|
||||
if {$testOptions(-verbose) > 0} {
|
||||
puts stdout $txt
|
||||
flush stdout
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
# http::Log
|
||||
#
|
||||
# A special-purpose logger used for running tests.
|
||||
# - Processes Log calls that have "^" in their arguments, and records them in
|
||||
# variable ::httpTest::testResults.
|
||||
# - Also writes them to stdout (using Puts) if ($testOptions(-verbose) > 0).
|
||||
# - Also writes Log calls that do not have "^", if ($testOptions(-verbose) > 1).
|
||||
|
||||
proc http::Log {args} {
|
||||
variable TestStartTimeInMs
|
||||
set time [expr {[clock milliseconds] - $TestStartTimeInMs}]
|
||||
set txt [list $time {*}$args]
|
||||
if {[string first ^ $txt] != -1} {
|
||||
::httpTest::LogRecord $txt
|
||||
::httpTest::Puts $txt
|
||||
} elseif {$::httpTest::testOptions(-verbose) > 1} {
|
||||
::httpTest::Puts $txt
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
# Called by http::Log (the "testing" version) to record logs for later analysis.
|
||||
|
||||
proc httpTest::LogRecord {txt} {
|
||||
variable testResults
|
||||
|
||||
set pos [string first ^ $txt]
|
||||
set len [string length $txt]
|
||||
if {$pos > $len - 3} {
|
||||
puts stdout "Logging Error: $txt"
|
||||
puts stdout "Fix this call to Log in http-*.tm so it has ^ then\
|
||||
a letter then a numeral."
|
||||
flush stdout
|
||||
} elseif {$pos == -1} {
|
||||
# Called by mistake.
|
||||
} else {
|
||||
set letter [string index $txt [incr pos]]
|
||||
set number [string index $txt [incr pos]]
|
||||
# Max 9 requests!
|
||||
lappend testResults [list $letter $number]
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Commands for analysing the logs recorded when calling http::geturl.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# httpTest::TestOverlaps --
|
||||
#
|
||||
# The main test for correct behaviour of pipelined and sequential
|
||||
# (non-pipelined) transactions. Other tests should be run first to detect
|
||||
# any inconsistencies in the data (e.g. absence of the elements that are
|
||||
# examined here).
|
||||
#
|
||||
# Examine the sequence $someResults for each transaction from 1 to $n,
|
||||
# ignoring any that are listed in $badTrans.
|
||||
# Determine whether the elements "B" to $term for one transaction overlap
|
||||
# elements "B" to $term for the previous and following transactions.
|
||||
#
|
||||
# Transactions in the list $badTrans are not included in "clean" or
|
||||
# "dirty", but their possible overlap with other transactions is noted.
|
||||
# Transactions in the list $notPiped are a subset of $badTrans, and
|
||||
# their possible overlap with other transactions is NOT noted.
|
||||
#
|
||||
# Arguments:
|
||||
# someResults - list of results, each of the form {letter numeral}
|
||||
# n - number of HTTP transactions
|
||||
# term - letter that indicated end of search range. "E" for testing
|
||||
# overlaps from start of request to end of response headers.
|
||||
# "F" to extend to the end of the response body.
|
||||
# msg - the cumulative message from sanity checks. Append to it only
|
||||
# to report a test failure.
|
||||
# badTrans - list of transaction numbers not to be assessed as "clean" or
|
||||
# "dirty"
|
||||
# notPiped - subset of badTrans. List of transaction numbers that cannot
|
||||
# taint another transaction by overlapping with it, because it
|
||||
# used a different socket.
|
||||
#
|
||||
# Return value: [list $msg $clean $dirty]
|
||||
# msg - warning messages: nothing will be appended to argument $msg if there
|
||||
# is an error with the test.
|
||||
# clean - list of transactions that have no overlap with other transactions
|
||||
# dirty - list of transactions that have YES overlap with other transactions
|
||||
|
||||
proc httpTest::TestOverlaps {someResults n term msg badTrans notPiped} {
|
||||
variable testOptions
|
||||
|
||||
# Check whether transactions overlap:
|
||||
set clean {}
|
||||
set dirty {}
|
||||
for {set i 1} {$i <= $n} {incr i} {
|
||||
if {$i in $badTrans} {
|
||||
continue
|
||||
}
|
||||
set myStart [lsearch -exact $someResults [list B $i]]
|
||||
set myEnd [lsearch -exact $someResults [list $term $i]]
|
||||
|
||||
if {($myStart == -1 || $myEnd == -1)} {
|
||||
set res "Cannot find positions of transaction $i"
|
||||
append msg $res \n
|
||||
Puts $res
|
||||
}
|
||||
|
||||
set overlaps {}
|
||||
for {set j $myStart} {$j <= $myEnd} {incr j} {
|
||||
lassign [lindex $someResults $j] letter number
|
||||
if {$number != $i && $letter ne "A" && $number ni $notPiped} {
|
||||
lappend overlaps $number
|
||||
}
|
||||
}
|
||||
|
||||
if {[llength $overlaps] == 0} {
|
||||
set res "Transaction $i has no overlaps"
|
||||
Puts $res
|
||||
lappend clean $i
|
||||
if {$testOptions(-dotted)} {
|
||||
# N.B. results from different segments are concatenated.
|
||||
lappend dirty .
|
||||
} else {
|
||||
}
|
||||
} else {
|
||||
set res "Transaction $i overlaps with [join $overlaps { }]"
|
||||
Puts $res
|
||||
lappend dirty $i
|
||||
if {$testOptions(-dotted)} {
|
||||
# N.B. results from different segments are concatenated.
|
||||
lappend clean .
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
return [list $msg $clean $dirty]
|
||||
}
|
||||
|
||||
# httpTest::PipelineNext --
|
||||
#
|
||||
# Test whether prevPair, pair are valid as consecutive elements of a pipelined
|
||||
# sequence (Start 1), (End 1), (Start 2), (End 2) ...
|
||||
# Numbers are integers increasing (by 1 if argument "any" is false), and need
|
||||
# not begin with 1.
|
||||
# The first element of the sequence has prevPair {} and is always passed as
|
||||
# valid.
|
||||
#
|
||||
# Arguments;
|
||||
# Start - string that labels the start of a segment
|
||||
# End - string that labels the end of a segment
|
||||
# prevPair - previous "pair" (list of string and number) element of a
|
||||
# sequence, or {} if argument "pair" is the first in the
|
||||
# sequence.
|
||||
# pair - current "pair" (list of string and number) element of a
|
||||
# sequence
|
||||
# any - (boolean) iff true, accept any increasing sequence of integers.
|
||||
# If false, integers must increase by 1.
|
||||
#
|
||||
# Return value - boolean, true iff the two pairs are valid consecutive elements.
|
||||
|
||||
proc httpTest::PipelineNext {Start End prevPair pair any} {
|
||||
if {$prevPair eq {}} {
|
||||
return 1
|
||||
}
|
||||
|
||||
lassign $prevPair letter number
|
||||
lassign $pair newLetter newNumber
|
||||
if {$letter eq $Start} {
|
||||
return [expr {($newLetter eq $End) && ($newNumber == $number)}]
|
||||
} elseif {$any} {
|
||||
set nxt [list $Start [expr {$number + 1}]]
|
||||
return [expr {($newLetter eq $Start) && ($newNumber > $number)}]
|
||||
} else {
|
||||
set nxt [list $Start [expr {$number + 1}]]
|
||||
return [expr {($newLetter eq $Start) && ($newNumber == $number + 1)}]
|
||||
}
|
||||
}
|
||||
|
||||
# httpTest::TestPipeline --
|
||||
#
|
||||
# Given a sequence of "pair" elements, check that the elements whose string is
|
||||
# $Start or $End form a valid pipeline. Ignore other elements.
|
||||
#
|
||||
# Return value: {} if valid pipeline, otherwise a non-empty error message.
|
||||
|
||||
proc httpTest::TestPipeline {someResults n Start End msg desc badTrans} {
|
||||
set sequence {}
|
||||
set prevPair {}
|
||||
set ok 1
|
||||
set any [llength $badTrans]
|
||||
foreach pair $someResults {
|
||||
lassign $pair letter number
|
||||
if {($letter in [list $Start $End]) && ($number ni $badTrans)} {
|
||||
lappend sequence $pair
|
||||
if {![PipelineNext $Start $End $prevPair $pair $any]} {
|
||||
set ok 0
|
||||
break
|
||||
}
|
||||
set prevPair $pair
|
||||
}
|
||||
}
|
||||
|
||||
if {!$ok} {
|
||||
set res "$desc are not pipelined: {$sequence}"
|
||||
append msg $res \n
|
||||
Puts $res
|
||||
}
|
||||
return $msg
|
||||
}
|
||||
|
||||
# httpTest::TestSequence --
|
||||
#
|
||||
# Examine each transaction from 1 to $n, ignoring any that are listed
|
||||
# in $badTrans.
|
||||
# Check that each transaction has elements A to F, in alphabetical order.
|
||||
|
||||
proc httpTest::TestSequence {someResults n msg badTrans} {
|
||||
variable testOptions
|
||||
|
||||
for {set i 1} {$i <= $n} {incr i} {
|
||||
if {$i in $badTrans} {
|
||||
continue
|
||||
}
|
||||
set sequence {}
|
||||
foreach pair $someResults {
|
||||
lassign $pair letter number
|
||||
if {$number == $i} {
|
||||
lappend sequence $letter
|
||||
}
|
||||
}
|
||||
if {$sequence eq {A B C D E F}} {
|
||||
} else {
|
||||
set res "Wrong sequence for token ::http::$i - {$sequence}"
|
||||
append msg $res \n
|
||||
Puts $res
|
||||
if {"X" in $sequence} {
|
||||
set res "- and error(s) X"
|
||||
append msg $res \n
|
||||
Puts $res
|
||||
}
|
||||
if {"Y" in $sequence} {
|
||||
set res "- and warnings(s) Y"
|
||||
append msg $res \n
|
||||
Puts $res
|
||||
}
|
||||
}
|
||||
}
|
||||
return $msg
|
||||
}
|
||||
|
||||
#
|
||||
# Arguments:
|
||||
# someResults - list of elements, each a list of a letter and a number
|
||||
# n - (positive integer) the number of HTTP requests
|
||||
# msg - accumulated warning messages
|
||||
# skipOverlaps - (boolean) whether to skip testing of transaction overlaps
|
||||
# badTrans - list of transaction numbers not to be assessed as "clean" or
|
||||
# "dirty" by their overlaps
|
||||
# for 1/2 includes all transactions
|
||||
# for 3/4 includes an increasing (with recursion) set that will not be included in the list because they are already handled.
|
||||
# notPiped - subset of badTrans. List of transaction numbers that cannot
|
||||
# taint another transaction by overlapping with it, because it
|
||||
# used a different socket.
|
||||
#
|
||||
# Return value: [list $msg $cleanE $cleanF $dirtyE $dirtyF]
|
||||
# msg - warning messages: nothing will be appended to argument $msg if there
|
||||
# is no error with the test.
|
||||
# cleanE - list of transactions that have no overlap with other transactions
|
||||
# (not considering response body)
|
||||
# dirtyE - list of transactions that have YES overlap with other transactions
|
||||
# (not considering response body)
|
||||
# cleanF - list of transactions that have no overlap with other transactions
|
||||
# (including response body)
|
||||
# dirtyF - list of transactions that have YES overlap with other transactions
|
||||
# (including response body)
|
||||
|
||||
proc httpTest::MostAnalysis {someResults n msg skipOverlaps badTrans notPiped} {
|
||||
variable testOptions
|
||||
|
||||
# Check that stages for "good" transactions are all present and correct:
|
||||
set msg [TestSequence $someResults $n $msg $badTrans]
|
||||
|
||||
# Check that requests are pipelined:
|
||||
set msg [TestPipeline $someResults $n B C $msg Requests $notPiped]
|
||||
|
||||
# Check that responses are pipelined:
|
||||
set msg [TestPipeline $someResults $n D F $msg Responses $notPiped]
|
||||
|
||||
if {$skipOverlaps} {
|
||||
set cleanE {}
|
||||
set dirtyE {}
|
||||
set cleanF {}
|
||||
set dirtyF {}
|
||||
} else {
|
||||
Puts "Overlaps including response body (test for non-pipelined case)"
|
||||
lassign [TestOverlaps $someResults $n F $msg $badTrans $notPiped] msg cleanF dirtyF
|
||||
|
||||
Puts "Overlaps without response body (test for pipelined case)"
|
||||
lassign [TestOverlaps $someResults $n E $msg $badTrans $notPiped] msg cleanE dirtyE
|
||||
}
|
||||
|
||||
return [list $msg $cleanE $cleanF $dirtyE $dirtyF]
|
||||
}
|
||||
|
||||
# httpTest::ProcessRetries --
|
||||
#
|
||||
# Command to examine results for socket-changing records [PQR],
|
||||
# divide the results into segments for each connection, and analyse each segment
|
||||
# individually.
|
||||
# (Could add $sock to the logging to simplify this, but never mind.)
|
||||
#
|
||||
# In each segment, identify any transactions that are not included, and
|
||||
# any that are aborted, to assist subsequent testing.
|
||||
#
|
||||
# Prepend A records (socket-independent) to each segment for transactions that
|
||||
# were scheduled (by A) but not completed (by F). Pass each segment to
|
||||
# MostAnalysis for processing.
|
||||
|
||||
proc httpTest::ProcessRetries {someResults n msg skipOverlaps notIncluded notPiped} {
|
||||
variable testOptions
|
||||
|
||||
set nextRetry [lsearch -glob -index 0 $someResults {[PQR]}]
|
||||
if {$nextRetry == -1} {
|
||||
return [MostAnalysis $someResults $n $msg $skipOverlaps $notIncluded $notPiped]
|
||||
}
|
||||
set badTrans $notIncluded
|
||||
set tryCount 0
|
||||
set try $nextRetry
|
||||
incr tryCount
|
||||
lassign [lindex $someResults $try] letter number
|
||||
Puts "Processing retry [lindex $someResults $try]"
|
||||
set beforeTry [lrange $someResults 0 $try-1]
|
||||
Puts [join $beforeTry \n]
|
||||
set afterTry [lrange $someResults $try+1 end]
|
||||
|
||||
set dummyTry {}
|
||||
for {set i 1} {$i <= $n} {incr i} {
|
||||
set first [lsearch -exact $beforeTry [list A $i]]
|
||||
set last [lsearch -exact $beforeTry [list F $i]]
|
||||
if {$first == -1} {
|
||||
set res "Transaction $i was not started in connection number $tryCount"
|
||||
# So lappend it to badTrans and don't include it in the call below of MostAnalysis.
|
||||
# append msg $res \n
|
||||
Puts $res
|
||||
if {$i ni $badTrans} {
|
||||
lappend badTrans $i
|
||||
} else {
|
||||
}
|
||||
} elseif {$last == -1} {
|
||||
set res "Transaction $i was started but unfinished in connection number $tryCount"
|
||||
# So lappend it to badTrans and don't include it in the call below of MostAnalysis.
|
||||
# append msg $res \n
|
||||
Puts $res
|
||||
lappend badTrans $i
|
||||
lappend dummyTry [list A $i]
|
||||
} else {
|
||||
set res "Transaction $i was started and finished in connection number $tryCount"
|
||||
# So include it in the call below of MostAnalysis.
|
||||
# So lappend it to notIncluded and don't include it in the recursive call of
|
||||
# ProcessRetries which handles the later connections.
|
||||
# append msg $res \n
|
||||
Puts $res
|
||||
lappend notIncluded $i
|
||||
}
|
||||
}
|
||||
|
||||
# Analyse the part of the results before the first replay:
|
||||
set HeadResults [MostAnalysis $beforeTry $n $msg $skipOverlaps $badTrans $notPiped]
|
||||
lassign $HeadResults msg cleanE1 cleanF1 dirtyE1 dirtyF1
|
||||
|
||||
# Pass the rest of the results to be processed recursively.
|
||||
set afterTry [concat $dummyTry $afterTry]
|
||||
set TailResults [ProcessRetries $afterTry $n $msg $skipOverlaps $notIncluded $notPiped]
|
||||
lassign $TailResults msg cleanE2 cleanF2 dirtyE2 dirtyF2
|
||||
|
||||
set cleanE [concat $cleanE1 $cleanE2]
|
||||
set cleanF [concat $cleanF1 $cleanF2]
|
||||
set dirtyE [concat $dirtyE1 $dirtyE2]
|
||||
set dirtyF [concat $dirtyF1 $dirtyF2]
|
||||
return [list $msg $cleanE $cleanF $dirtyE $dirtyF]
|
||||
}
|
||||
|
||||
# httpTest::logAnalyse --
|
||||
#
|
||||
# The main command called to analyse logs for a single test.
|
||||
#
|
||||
# Arguments:
|
||||
# n - (positive integer) the number of HTTP requests
|
||||
# skipOverlaps - (boolean) whether to skip testing of transaction overlaps
|
||||
# notIncluded - list of transaction numbers not to be assessed as "clean" or
|
||||
# "dirty" by their overlaps
|
||||
# notPiped - subset of notIncluded. List of transaction numbers that cannot
|
||||
# taint another transaction by overlapping with it, because it
|
||||
# used a different socket.
|
||||
#
|
||||
# Return value: [list $msg $cleanE $cleanF $dirtyE $dirtyF]
|
||||
# msg - warning messages: {} if there is no error with the test.
|
||||
# cleanE - list of transactions that have no overlap with other transactions
|
||||
# (not considering response body)
|
||||
# dirtyE - list of transactions that have YES overlap with other transactions
|
||||
# (not considering response body)
|
||||
# cleanF - list of transactions that have no overlap with other transactions
|
||||
# (including response body)
|
||||
# dirtyF - list of transactions that have YES overlap with other transactions
|
||||
# (including response body)
|
||||
|
||||
proc httpTest::logAnalyse {n skipOverlaps notIncluded notPiped} {
|
||||
variable testResults
|
||||
variable testOptions
|
||||
|
||||
# Check that each data item has the correct form {letter numeral}.
|
||||
set ii 0
|
||||
set ok 1
|
||||
foreach pair $testResults {
|
||||
lassign $pair letter number
|
||||
if { [string match {[A-Z]} $letter]
|
||||
&& [string match {[0-9]} $number]
|
||||
} {
|
||||
# OK
|
||||
} else {
|
||||
set ok 0
|
||||
set res "Error: testResults has bad element {$pair} at position $ii"
|
||||
append msg $res \n
|
||||
Puts $res
|
||||
}
|
||||
incr ii
|
||||
}
|
||||
|
||||
if {!$ok} {
|
||||
return $msg
|
||||
}
|
||||
set msg {}
|
||||
|
||||
Puts [join $testResults \n]
|
||||
ProcessRetries $testResults $n $msg $skipOverlaps $notIncluded $notPiped
|
||||
# N.B. Implicit Return.
|
||||
}
|
||||
|
||||
proc httpTest::cleanupHttpTest {} {
|
||||
variable testResults
|
||||
set testResults {}
|
||||
return
|
||||
}
|
||||
|
||||
proc httpTest::setHttpTestOptions {key args} {
|
||||
variable testOptions
|
||||
if {$key ni {-dotted -verbose}} {
|
||||
return -code error {valid options are -dotted, -verbose}
|
||||
}
|
||||
set testOptions($key) {*}$args
|
||||
}
|
||||
|
||||
namespace eval httpTest {
|
||||
namespace export cleanupHttpTest logAnalyse setHttpTestOptions
|
||||
}
|
||||
@@ -0,0 +1,509 @@
|
||||
# httpTestScript.tcl
|
||||
#
|
||||
# Test HTTP/1.1 concurrent requests including
|
||||
# queueing, pipelining and retries.
|
||||
#
|
||||
# Copyright (C) 2018 Keith Nash <kjnash@users.sourceforge.net>
|
||||
#
|
||||
# See the file "license.terms" for information on usage and redistribution
|
||||
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# "Package" httpTestScript for executing test scripts written in a convenient
|
||||
# shorthand.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Documentation for "package" httpTestScript.
|
||||
# ------------------------------------------------------------------------------
|
||||
# To use the package:
|
||||
# (a) define URLs as the values of elements in the array ::httpTestScript
|
||||
# (b) define a script in terms of the commands
|
||||
# START STOP DELAY KEEPALIVE WAIT PIPELINE GET HEAD POST
|
||||
# referring to URLs by the name of the corresponding array element. The
|
||||
# script can include any other Tcl commands, and evaluates in the
|
||||
# httpTestScript namespace.
|
||||
# (c) Use the command httpTestScript::runHttpTestScript to evaluate the script.
|
||||
# (d) For tcltest tests, wrap the runHttpTestScript call in a suitable "test"
|
||||
# command.
|
||||
# ------------------------------------------------------------------------------
|
||||
# START
|
||||
# Must be the first command of the script.
|
||||
#
|
||||
# STOP
|
||||
# Must be present in the script to avoid waiting for client timeout.
|
||||
# Usually the last command, but can be elsewhere to end a script prematurely.
|
||||
# Subsequent httpTestScript commands will have no effect.
|
||||
#
|
||||
# DELAY ms
|
||||
# If there are no WAIT commands, this sets the delay in ms between subsequent
|
||||
# calls to http::geturl. Default 500ms.
|
||||
#
|
||||
# KEEPALIVE
|
||||
# Set the value passed to http::geturl for the -keepalive option. The command
|
||||
# applies to subsequent requests in the script. Default 1.
|
||||
#
|
||||
# WAIT ms
|
||||
# Pause for a time in ms before sending subsequent requests.
|
||||
#
|
||||
# PIPELINE boolean
|
||||
# Set the value of -pipeline using http::config. The last PIPELINE command
|
||||
# in the script applies to every request. Default 1.
|
||||
#
|
||||
# POSTFRESH boolean
|
||||
# Set the value of -postfresh using http::config. The last POSTFRESH command
|
||||
# in the script applies to every request. Default 0.
|
||||
#
|
||||
# REPOST boolean
|
||||
# Set the value of -repost using http::config. The last REPOST command
|
||||
# in the script applies to every request. Default 1 for httpTestScript.
|
||||
# (Default value in http is 0).
|
||||
#
|
||||
# GET uriCode ?arg ...?
|
||||
# Send a HTTP request using the GET method.
|
||||
# Arguments:
|
||||
# uriCode - the code for the base URI - the value must be stored in
|
||||
# ::httpTestScript::URL($uriCode).
|
||||
# args - strings that will be joined by "&" and appended to the query
|
||||
# string with a preceding "&".
|
||||
#
|
||||
# HEAD uriCode ?arg ...?
|
||||
# Send a HTTP request using the HEAD method.
|
||||
# Arguments: as for GET
|
||||
#
|
||||
# POST uriCode ?arg ...?
|
||||
# Send a HTTP request using the POST method.
|
||||
# Arguments:
|
||||
# uriCode - the code for the base URI - the value must be stored in
|
||||
# ::httpTestScript::URL($uriCode).
|
||||
# args - strings that will be joined by "&" and used as the request body.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
namespace eval ::httpTestScript {
|
||||
namespace export runHttpTestScript cleanupHttpTestScript
|
||||
}
|
||||
|
||||
# httpTestScript::START --
|
||||
# Initialise, and create a long-stop timeout.
|
||||
|
||||
proc httpTestScript::START {} {
|
||||
variable CountRequestedSoFar
|
||||
variable RequestsWhenStopped
|
||||
variable KeepAlive
|
||||
variable Delay
|
||||
variable TimeOutCode
|
||||
variable TimeOutDone
|
||||
variable StartDone
|
||||
variable StopDone
|
||||
variable CountFinishedSoFar
|
||||
variable RequestList
|
||||
variable RequestsMade
|
||||
variable ExtraTime
|
||||
variable ActualKeepAlive
|
||||
|
||||
if {[info exists StartDone] && ($StartDone == 1)} {
|
||||
set msg {START has been called twice without an intervening STOP}
|
||||
return -code error $msg
|
||||
}
|
||||
|
||||
set StartDone 1
|
||||
set StopDone 0
|
||||
set TimeOutDone 0
|
||||
set CountFinishedSoFar 0
|
||||
set CountRequestedSoFar 0
|
||||
set RequestList {}
|
||||
set RequestsMade {}
|
||||
set ExtraTime 0
|
||||
set ActualKeepAlive 1
|
||||
|
||||
# Undefined until a STOP command:
|
||||
unset -nocomplain RequestsWhenStopped
|
||||
|
||||
# Default values:
|
||||
set KeepAlive 1
|
||||
set Delay 500
|
||||
|
||||
# Default values for tests:
|
||||
KEEPALIVE 1
|
||||
PIPELINE 1
|
||||
POSTFRESH 0
|
||||
REPOST 1
|
||||
|
||||
set TimeOutCode [after 30000 httpTestScript::TimeOutNow]
|
||||
# set TimeOutCode [after 4000 httpTestScript::TimeOutNow]
|
||||
return
|
||||
}
|
||||
|
||||
# httpTestScript::STOP --
|
||||
# Do not process any more commands. The commands will be executed but will
|
||||
# silently do nothing.
|
||||
|
||||
proc httpTestScript::STOP {} {
|
||||
variable CountRequestedSoFar
|
||||
variable CountFinishedSoFar
|
||||
variable RequestsWhenStopped
|
||||
variable TimeOutCode
|
||||
variable StartDone
|
||||
variable StopDone
|
||||
variable RequestsMade
|
||||
|
||||
if {$StopDone} {
|
||||
# Don't do anything on a second call.
|
||||
return
|
||||
}
|
||||
|
||||
if {![info exists StartDone]} {
|
||||
return -code error {initialise the script by calling command START}
|
||||
}
|
||||
|
||||
set StopDone 1
|
||||
set StartDone 0
|
||||
set RequestsWhenStopped $CountRequestedSoFar
|
||||
unset -nocomplain StartDone
|
||||
|
||||
if {$CountFinishedSoFar == $RequestsWhenStopped} {
|
||||
if {[info exists TimeOutCode]} {
|
||||
after cancel $TimeOutCode
|
||||
}
|
||||
set ::httpTestScript::FOREVER 0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
# httpTestScript::DELAY --
|
||||
# If there are no WAIT commands, this sets the delay in ms between subsequent
|
||||
# calls to http::geturl. Default 500ms.
|
||||
|
||||
proc httpTestScript::DELAY {t} {
|
||||
variable StartDone
|
||||
variable StopDone
|
||||
|
||||
if {$StopDone} {
|
||||
return
|
||||
}
|
||||
|
||||
if {![info exists StartDone]} {
|
||||
return -code error {initialise the script by calling command START}
|
||||
}
|
||||
|
||||
variable Delay
|
||||
|
||||
set Delay $t
|
||||
return
|
||||
}
|
||||
|
||||
# httpTestScript::KEEPALIVE --
|
||||
# Set the value passed to http::geturl for the -keepalive option. Default 1.
|
||||
|
||||
proc httpTestScript::KEEPALIVE {b} {
|
||||
variable StartDone
|
||||
variable StopDone
|
||||
|
||||
if {$StopDone} {
|
||||
return
|
||||
}
|
||||
|
||||
if {![info exists StartDone]} {
|
||||
return -code error {initialise the script by calling command START}
|
||||
}
|
||||
|
||||
variable KeepAlive
|
||||
set KeepAlive $b
|
||||
return
|
||||
}
|
||||
|
||||
# httpTestScript::WAIT --
|
||||
# Pause for a time in ms before processing any more commands.
|
||||
|
||||
proc httpTestScript::WAIT {t} {
|
||||
variable StartDone
|
||||
variable StopDone
|
||||
variable ExtraTime
|
||||
|
||||
if {$StopDone} {
|
||||
return
|
||||
}
|
||||
|
||||
if {![info exists StartDone]} {
|
||||
return -code error {initialise the script by calling command START}
|
||||
}
|
||||
|
||||
if {(![string is integer -strict $t]) || $t < 0} {
|
||||
return -code error {argument to WAIT must be a non-negative integer}
|
||||
}
|
||||
|
||||
incr ExtraTime $t
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
# httpTestScript::PIPELINE --
|
||||
# Pass a value to http::config -pipeline.
|
||||
|
||||
proc httpTestScript::PIPELINE {b} {
|
||||
variable StartDone
|
||||
variable StopDone
|
||||
|
||||
if {$StopDone} {
|
||||
return
|
||||
}
|
||||
|
||||
if {![info exists StartDone]} {
|
||||
return -code error {initialise the script by calling command START}
|
||||
}
|
||||
|
||||
::http::config -pipeline $b
|
||||
##::http::Log http(-pipeline) is now [::http::config -pipeline]
|
||||
return
|
||||
}
|
||||
|
||||
# httpTestScript::POSTFRESH --
|
||||
# Pass a value to http::config -postfresh.
|
||||
|
||||
proc httpTestScript::POSTFRESH {b} {
|
||||
variable StartDone
|
||||
variable StopDone
|
||||
|
||||
if {$StopDone} {
|
||||
return
|
||||
}
|
||||
|
||||
if {![info exists StartDone]} {
|
||||
return -code error {initialise the script by calling command START}
|
||||
}
|
||||
|
||||
::http::config -postfresh $b
|
||||
##::http::Log http(-postfresh) is now [::http::config -postfresh]
|
||||
return
|
||||
}
|
||||
|
||||
# httpTestScript::REPOST --
|
||||
# Pass a value to http::config -repost.
|
||||
|
||||
proc httpTestScript::REPOST {b} {
|
||||
variable StartDone
|
||||
variable StopDone
|
||||
|
||||
if {$StopDone} {
|
||||
return
|
||||
}
|
||||
|
||||
if {![info exists StartDone]} {
|
||||
return -code error {initialise the script by calling command START}
|
||||
}
|
||||
|
||||
::http::config -repost $b
|
||||
##::http::Log http(-repost) is now [::http::config -repost]
|
||||
return
|
||||
}
|
||||
|
||||
# httpTestScript::GET --
|
||||
# Send a HTTP request using the GET method.
|
||||
# Arguments:
|
||||
# uriCode - the code for the base URI - the value must be stored in
|
||||
# ::httpTestScript::URL($uriCode).
|
||||
# args - strings that will each be preceded by "&" and appended to the query
|
||||
# string.
|
||||
|
||||
proc httpTestScript::GET {uriCode args} {
|
||||
variable RequestList
|
||||
lappend RequestList GET
|
||||
RequestAfter $uriCode 0 {} {*}$args
|
||||
return
|
||||
}
|
||||
|
||||
# httpTestScript::HEAD --
|
||||
# Send a HTTP request using the HEAD method.
|
||||
# Arguments: as for GET
|
||||
|
||||
proc httpTestScript::HEAD {uriCode args} {
|
||||
variable RequestList
|
||||
lappend RequestList HEAD
|
||||
RequestAfter $uriCode 1 {} {*}$args
|
||||
return
|
||||
}
|
||||
|
||||
# httpTestScript::POST --
|
||||
# Send a HTTP request using the POST method.
|
||||
# Arguments:
|
||||
# uriCode - the code for the base URI - the value must be stored in
|
||||
# ::httpTestScript::URL($uriCode).
|
||||
# args - strings that will be joined by "&" and used as the request body.
|
||||
|
||||
proc httpTestScript::POST {uriCode args} {
|
||||
variable RequestList
|
||||
lappend RequestList POST
|
||||
RequestAfter $uriCode 0 {use} {*}$args
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
proc httpTestScript::RequestAfter {uriCode validate query args} {
|
||||
variable CountRequestedSoFar
|
||||
variable Delay
|
||||
variable ExtraTime
|
||||
variable StartDone
|
||||
variable StopDone
|
||||
variable KeepAlive
|
||||
|
||||
if {$StopDone} {
|
||||
return
|
||||
}
|
||||
|
||||
if {![info exists StartDone]} {
|
||||
return -code error {initialise the script by calling command START}
|
||||
}
|
||||
|
||||
incr CountRequestedSoFar
|
||||
set idelay [expr {($CountRequestedSoFar - 1) * $Delay + 10 + $ExtraTime}]
|
||||
|
||||
# Could pass values of -pipeline, -postfresh, -repost if it were
|
||||
# useful to change these mid-script.
|
||||
after $idelay [list httpTestScript::Requester $uriCode $KeepAlive $validate $query {*}$args]
|
||||
return
|
||||
}
|
||||
|
||||
proc httpTestScript::Requester {uriCode keepAlive validate query args} {
|
||||
variable URL
|
||||
|
||||
::http::config -accept {*/*}
|
||||
|
||||
set absUrl $URL($uriCode)
|
||||
if {$query eq {}} {
|
||||
if {$args ne {}} {
|
||||
append absUrl & [join $args &]
|
||||
}
|
||||
set queryArgs {}
|
||||
} elseif {$validate} {
|
||||
return -code error {cannot have both -validate (HEAD) and -query (POST)}
|
||||
} else {
|
||||
set queryArgs [list -query [join $args &]]
|
||||
}
|
||||
|
||||
if {[catch {
|
||||
::http::geturl $absUrl \
|
||||
-validate $validate \
|
||||
-timeout 10000 \
|
||||
{*}$queryArgs \
|
||||
-keepalive $keepAlive \
|
||||
-command ::httpTestScript::WhenFinished
|
||||
} token]} {
|
||||
set msg $token
|
||||
catch {puts stdout "Error: $msg"}
|
||||
return
|
||||
} else {
|
||||
# Request will begin.
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
proc httpTestScript::TimeOutNow {} {
|
||||
variable TimeOutDone
|
||||
|
||||
set TimeOutDone 1
|
||||
set ::httpTestScript::FOREVER 0
|
||||
return
|
||||
}
|
||||
|
||||
proc httpTestScript::WhenFinished {hToken} {
|
||||
variable CountFinishedSoFar
|
||||
variable RequestsWhenStopped
|
||||
variable TimeOutCode
|
||||
variable StopDone
|
||||
variable RequestList
|
||||
variable RequestsMade
|
||||
variable ActualKeepAlive
|
||||
|
||||
upvar #0 $hToken state
|
||||
|
||||
if {[catch {
|
||||
if { [info exists state(transfer)]
|
||||
&& ($state(transfer) eq "chunked")
|
||||
} {
|
||||
set Trans chunked
|
||||
} else {
|
||||
set Trans unchunked
|
||||
}
|
||||
|
||||
if { [info exists ::httpTest::testOptions(-verbose)]
|
||||
&& ($::httpTest::testOptions(-verbose) > 0)
|
||||
} {
|
||||
puts "Token $hToken
|
||||
Response $state(http)
|
||||
Status $state(status)
|
||||
Method $state(method)
|
||||
Transfer $Trans
|
||||
Size $state(currentsize)
|
||||
URL $state(url)
|
||||
"
|
||||
}
|
||||
|
||||
if {!$state(-keepalive)} {
|
||||
set ActualKeepAlive 0
|
||||
}
|
||||
|
||||
if {[info exists state(method)]} {
|
||||
lappend RequestsMade $state(method)
|
||||
} else {
|
||||
lappend RequestsMade UNKNOWN
|
||||
}
|
||||
set tk [namespace tail $hToken]
|
||||
|
||||
if { ($state(http) != {HTTP/1.1 200 OK})
|
||||
|| ($state(status) != {ok})
|
||||
|| (($state(currentsize) == 0) && ($state(method) ne "HEAD"))
|
||||
} {
|
||||
::http::Log ^X$tk unexpected result Response $state(http) Status $state(status) Size $state(currentsize) - token $hToken
|
||||
}
|
||||
} err]} {
|
||||
::http::Log ^X$tk httpTestScript::WhenFinished failed with error status: $err - token $hToken
|
||||
}
|
||||
|
||||
incr CountFinishedSoFar
|
||||
if {$StopDone && ($CountFinishedSoFar == $RequestsWhenStopped)} {
|
||||
if {[info exists TimeOutCode]} {
|
||||
after cancel $TimeOutCode
|
||||
}
|
||||
if {$RequestsMade ne $RequestList && $ActualKeepAlive} {
|
||||
::http::Log ^X$tk unexpected result - Script asked for "{$RequestList}" but got "{$RequestsMade}" - token $hToken
|
||||
}
|
||||
set ::httpTestScript::FOREVER 0
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
proc httpTestScript::runHttpTestScript {scr} {
|
||||
variable TimeOutDone
|
||||
variable RequestsWhenStopped
|
||||
|
||||
after idle [list namespace eval ::httpTestScript $scr]
|
||||
vwait ::httpTestScript::FOREVER
|
||||
# N.B. does not automatically execute in this namespace, unlike some other events.
|
||||
# Release when all requests have been served or have timed out.
|
||||
|
||||
if {$TimeOutDone} {
|
||||
return -code error {test script timed out}
|
||||
}
|
||||
|
||||
return $RequestsWhenStopped
|
||||
}
|
||||
|
||||
|
||||
proc httpTestScript::cleanupHttpTestScript {} {
|
||||
variable TimeOutDone
|
||||
variable RequestsWhenStopped
|
||||
|
||||
if {![info exists RequestsWhenStopped]} {
|
||||
return -code error {Cleanup Failed: RequestsWhenStopped is undefined}
|
||||
}
|
||||
|
||||
for {set i 1} {$i <= $RequestsWhenStopped} {incr i} {
|
||||
http::cleanup ::http::$i
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
+4
-4
@@ -100,10 +100,10 @@ test lreplace-1.26 {lreplace command} {
|
||||
} {a {} {}}
|
||||
test lreplace-1.27 {lreplace command} -body {
|
||||
lreplace x 1 1
|
||||
} -returnCodes 1 -result {list doesn't contain element 1}
|
||||
} -result x
|
||||
test lreplace-1.28 {lreplace command} -body {
|
||||
lreplace x 1 1 y
|
||||
} -returnCodes 1 -result {list doesn't contain element 1}
|
||||
} -result {x y}
|
||||
test lreplace-1.29 {lreplace command} -body {
|
||||
lreplace x 1 1 [error foo]
|
||||
} -returnCodes 1 -result {foo}
|
||||
@@ -128,10 +128,10 @@ test lreplace-2.5 {lreplace errors} {
|
||||
} {1 {bad index "1x": must be integer?[+-]integer? or end?[+-]integer?}}
|
||||
test lreplace-2.6 {lreplace errors} {
|
||||
list [catch {lreplace x 3 2} msg] $msg
|
||||
} {1 {list doesn't contain element 3}}
|
||||
} {0 x}
|
||||
test lreplace-2.7 {lreplace errors} {
|
||||
list [catch {lreplace x 2 2} msg] $msg
|
||||
} {1 {list doesn't contain element 2}}
|
||||
} {0 x}
|
||||
|
||||
test lreplace-3.1 {lreplace won't modify shared argument objects} {
|
||||
proc p {} {
|
||||
|
||||
+11
-11
@@ -99,7 +99,7 @@ test macOSXFCmd-2.6 {MacOSXSetFileAttribute - hidden} {macosxFileAttr notRoot} {
|
||||
[catch {file attributes foo.test -hidden} msg] $msg \
|
||||
[file delete -force -- foo.test]
|
||||
} {0 {} 0 1 {}}
|
||||
test macOSXFCmd-2.7 {MacOSXSetFileAttribute - rsrclength} {macosxFileAttr notRoot} {
|
||||
test macOSXFCmd-2.7 {MacOSXSetFileAttribute - rsrclength} {macosxFileAttr notRoot nonPortable} {
|
||||
catch {file delete -force -- foo.test}
|
||||
close [open foo.test w]
|
||||
catch {
|
||||
@@ -151,16 +151,16 @@ test macOSXFCmd-4.1 {TclMacOSXMatchType} {macosxFileAttr notRoot} {
|
||||
file attributes dir.test -hidden 1
|
||||
}
|
||||
set res [list \
|
||||
[catch {glob *.test} msg] $msg \
|
||||
[catch {glob -types FOOT *.test} msg] $msg \
|
||||
[catch {glob -types {{macintosh type FOOT}} *.test} msg] $msg \
|
||||
[catch {glob -types FOOTT *.test} msg] $msg \
|
||||
[catch {glob -types {{macintosh type FOOTT}} *.test} msg] $msg \
|
||||
[catch {glob -types {{macintosh type {}}} *.test} msg] $msg \
|
||||
[catch {glob -types {{macintosh creator FOOC}} *.test} msg] $msg \
|
||||
[catch {glob -types {{macintosh creator FOOC} {macintosh type FOOT}} *.test} msg] $msg \
|
||||
[catch {glob -types hidden *.test} msg] $msg \
|
||||
[catch {glob -types {hidden FOOT} *.test} msg] $msg \
|
||||
[catch {lsort [glob *.test]} msg] $msg \
|
||||
[catch {lsort [glob -types FOOT *.test]} msg] $msg \
|
||||
[catch {lsort [glob -types {{macintosh type FOOT}} *.test]} msg] $msg \
|
||||
[catch {lsort [glob -types FOOTT *.test]} msg] $msg \
|
||||
[catch {lsort [glob -types {{macintosh type FOOTT}} *.test]} msg] $msg \
|
||||
[catch {lsort [glob -types {{macintosh type {}}} *.test]} msg] $msg \
|
||||
[catch {lsort [glob -types {{macintosh creator FOOC}} *.test]} msg] $msg \
|
||||
[catch {lsort [glob -types {{macintosh creator FOOC} {macintosh type FOOT}} *.test]} msg] $msg \
|
||||
[catch {lsort [glob -types hidden *.test]} msg] $msg \
|
||||
[catch {lsort [glob -types {hidden FOOT} *.test]} msg] $msg \
|
||||
]
|
||||
cd ..
|
||||
file delete -force globtest
|
||||
|
||||
@@ -1804,6 +1804,90 @@ test oo-13.4 {OO: changing an object's class} -body {
|
||||
foo destroy
|
||||
bar destroy
|
||||
} -result {::foo ::foo ::foo ::bar}
|
||||
test oo-13.5 {OO: changing an object's class: non-class to class} -setup {
|
||||
oo::object create fooObj
|
||||
} -body {
|
||||
oo::objdefine fooObj {
|
||||
class oo::class
|
||||
}
|
||||
oo::define fooObj {
|
||||
method x {} {expr 1+2+3}
|
||||
}
|
||||
[fooObj new] x
|
||||
} -cleanup {
|
||||
fooObj destroy
|
||||
} -result 6
|
||||
test oo-13.6 {OO: changing an object's class: class to non-class} -setup {
|
||||
oo::class create foo
|
||||
unset -nocomplain ::result
|
||||
} -body {
|
||||
set result dangling
|
||||
oo::define foo {
|
||||
method x {} {expr 1+2+3}
|
||||
}
|
||||
oo::class create boo {
|
||||
superclass foo
|
||||
destructor {set ::result "ok"}
|
||||
}
|
||||
boo new
|
||||
foo create bar
|
||||
oo::objdefine foo {
|
||||
class oo::object
|
||||
}
|
||||
list $result [catch {bar x} msg] $msg
|
||||
} -cleanup {
|
||||
catch {bar destroy}
|
||||
foo destroy
|
||||
} -result {ok 1 {invalid command name "bar"}}
|
||||
test oo-13.7 {OO: changing an object's class} -setup {
|
||||
oo::class create foo
|
||||
oo::class create bar
|
||||
unset -nocomplain result
|
||||
} -body {
|
||||
oo::define bar method x {} {return ok}
|
||||
oo::define foo {
|
||||
method x {} {expr 1+2+3}
|
||||
self mixin foo
|
||||
}
|
||||
lappend result [foo x]
|
||||
oo::objdefine foo class bar
|
||||
lappend result [foo x]
|
||||
} -cleanup {
|
||||
foo destroy
|
||||
bar destroy
|
||||
} -result {6 ok}
|
||||
test oo-13.8 {OO: changing an object's class to itself} -setup {
|
||||
oo::class create foo
|
||||
} -body {
|
||||
oo::define foo {
|
||||
method x {} {expr 1+2+3}
|
||||
}
|
||||
oo::objdefine foo class foo
|
||||
} -cleanup {
|
||||
foo destroy
|
||||
} -returnCodes error -result {may not change classes into an instance of themselves}
|
||||
test oo-13.9 {OO: changing an object's class: roots are special} -setup {
|
||||
set i [interp create]
|
||||
} -body {
|
||||
$i eval {
|
||||
oo::objdefine oo::object {
|
||||
class oo::class
|
||||
}
|
||||
}
|
||||
} -cleanup {
|
||||
interp delete $i
|
||||
} -returnCodes error -result {may not modify the class of the root object class}
|
||||
test oo-13.10 {OO: changing an object's class: roots are special} -setup {
|
||||
set i [interp create]
|
||||
} -body {
|
||||
$i eval {
|
||||
oo::objdefine oo::class {
|
||||
class oo::object
|
||||
}
|
||||
}
|
||||
} -cleanup {
|
||||
interp delete $i
|
||||
} -returnCodes error -result {may not modify the class of the class of classes}
|
||||
# todo: changing a class subtype (metaclass) to another class subtype
|
||||
|
||||
test oo-14.1 {OO: mixins} {
|
||||
|
||||
@@ -110,6 +110,14 @@ test proc-1.8 {Tcl_ProcObjCmd, check that formal parameter names are simple name
|
||||
proc p {b:a b::a} {
|
||||
}
|
||||
} -returnCodes error -result {formal parameter "b::a" is not a simple name}
|
||||
test proc-1.9 {Tcl_ProcObjCmd, arguments via canonical list (string-representation bug [631b4c45df])} -body {
|
||||
set v 2
|
||||
binary scan AB cc a b
|
||||
proc p [list [list a $a] [list b $b] [list v [expr {$v + 2}]]] {expr {$a + $b + $v}}
|
||||
p
|
||||
} -result [expr {65+66+4}] -cleanup {
|
||||
rename p {}
|
||||
}
|
||||
|
||||
test proc-2.1 {TclFindProc, simple proc name and proc not in namespace} -setup {
|
||||
catch {namespace delete {*}[namespace children :: test_ns_*]}
|
||||
@@ -383,6 +391,14 @@ test proc-7.4 {Proc struct outlives its interp: Bug 3532959} {
|
||||
interp delete slave
|
||||
unset lambda
|
||||
} {}
|
||||
|
||||
test proc-7.5 {[631b4c45df] Crash in argument processing} {
|
||||
binary scan A c val
|
||||
proc foo [list [list from $val]] {}
|
||||
rename foo {}
|
||||
unset -nocomplain val
|
||||
} {}
|
||||
|
||||
|
||||
# cleanup
|
||||
catch {rename p ""}
|
||||
|
||||
+3
-4
@@ -57,11 +57,10 @@ proc cleanup {args} {
|
||||
}
|
||||
|
||||
if {[testConstraint winOnly]} {
|
||||
set major [string index $tcl_platform(osVersion) 0]
|
||||
if {[testConstraint nt] && $major > 4} {
|
||||
if {$major > 5} {
|
||||
if {[testConstraint nt] && $::tcl_platform(osVersion) >= 5.0} {
|
||||
if {$::tcl_platform(osVersion) >= 6.0} {
|
||||
testConstraint winVista 1
|
||||
} elseif {$major == 5} {
|
||||
} else {
|
||||
testConstraint win2000orXP 1
|
||||
}
|
||||
} else {
|
||||
|
||||
+223
-62
@@ -30,6 +30,7 @@ testConstraint cat32 [file exists $cat32]
|
||||
testConstraint AllocConsole [catch {puts console1 ""}]
|
||||
testConstraint RealConsole [expr {![testConstraint AllocConsole]}]
|
||||
testConstraint testexcept [llength [info commands testexcept]]
|
||||
testConstraint slowTest 0
|
||||
|
||||
|
||||
set big bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n
|
||||
@@ -41,7 +42,7 @@ append big $big
|
||||
append big $big
|
||||
|
||||
set path(little) [makeFile {} little]
|
||||
set f [open $path(little) w]
|
||||
set f [open $path(little) w]
|
||||
puts -nonewline $f "little"
|
||||
close $f
|
||||
|
||||
@@ -308,9 +309,54 @@ test winpipe-6.2 {PipeSetupProc & PipeCheckProc: write threads} \
|
||||
lappend x [catch {close $f} msg] $msg
|
||||
} {writable timeout 0 {}}
|
||||
|
||||
set path(echoArgs.tcl) [makeFile {
|
||||
puts "[list $argv0 $argv]"
|
||||
} echoArgs.tcl]
|
||||
proc _testExecArgs {single args} {
|
||||
variable path
|
||||
if {![info exists path(echoArgs.tcl)] || ![file exists $path(echoArgs.tcl)]} {
|
||||
set path(echoArgs.tcl) [makeFile {
|
||||
puts "[list [file tail $argv0] {*}$argv]"
|
||||
} echoArgs.tcl]
|
||||
}
|
||||
if {![info exists path(echoArgs.bat)] || ![file exists $path(echoArgs.bat)]} {
|
||||
set path(echoArgs.bat) [makeFile "@[file native [interpreter]] $path(echoArgs.tcl) %*" "echoArgs.bat"]
|
||||
}
|
||||
set cmds [list [list [interpreter] $path(echoArgs.tcl)]]
|
||||
if {!($single & 2)} {
|
||||
lappend cmds [list $path(echoArgs.bat)]
|
||||
} else {
|
||||
if {![info exists path(echoArgs2.bat)] || ![file exists $path(echoArgs2.bat)]} {
|
||||
set path(echoArgs2.bat) [makeFile \
|
||||
"@[file native [interpreter]] $path(echoArgs.tcl) %*" \
|
||||
"echo(Cmd)Test Args & Batch.bat" [makeDirectory test(Dir)Check]]
|
||||
}
|
||||
lappend cmds [list $path(echoArgs2.bat)]
|
||||
}
|
||||
set broken {}
|
||||
foreach args $args {
|
||||
if {$single & 1} {
|
||||
# enclose single test-arg between 1st/3rd to be sure nothing is truncated
|
||||
# (e. g. to cover unexpected trim by nts-zero case, and args don't recombined):
|
||||
set args [list "1st" $args "3rd"]
|
||||
}
|
||||
set args [list {*}$args]; # normalized canonical list
|
||||
foreach cmd $cmds {
|
||||
set e [linsert $args 0 [file tail $path(echoArgs.tcl)]]
|
||||
tcltest::DebugPuts 4 " ## test exec [file extension [lindex $cmd 0]] ($cmd) for\n ## $args"
|
||||
if {[catch {
|
||||
exec {*}$cmd {*}$args
|
||||
} r]} {
|
||||
set r "ERROR: $r"
|
||||
}
|
||||
if {$r ne $e} {
|
||||
append broken "\[ERROR\]: exec [file extension [lindex $cmd 0]] on $args\n -- result:\n$r\n -- expected:\n$e\n"
|
||||
}
|
||||
if {$single & 8} {
|
||||
# if test exe only:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return $broken
|
||||
}
|
||||
|
||||
### validate the raw output of BuildCommandLine().
|
||||
###
|
||||
@@ -369,65 +415,178 @@ test winpipe-7.18 {BuildCommandLine: special chars #5} {win exec} {
|
||||
exec $env(COMSPEC) /c echo foo \} bar
|
||||
} "foo \} bar"
|
||||
|
||||
set injectList {
|
||||
{test"whoami} {test""whoami}
|
||||
{test"""whoami} {test""""whoami}
|
||||
|
||||
"test\"whoami\\" "test\"\"whoami\\"
|
||||
"test\"\"\"whoami\\" "test\"\"\"\"whoami\\"
|
||||
|
||||
{test\\&\\test} {test"\\&\\test}
|
||||
{"test\\&\\test} {"test"\\&\\"test"}
|
||||
{test\\"&"\\test} {test"\\"&"\\test}
|
||||
{"test\\"&"\\test} {"test"\\"&"\\"test"}
|
||||
|
||||
{test\"&whoami} {test"\"&whoami}
|
||||
{test""\"&whoami} {test"""\"&whoami}
|
||||
{test\"\&whoami} {test"\"\&whoami}
|
||||
{test""\"\&whoami} {test"""\"\&whoami}
|
||||
|
||||
{test&whoami} {test|whoami}
|
||||
{"test&whoami} {"test|whoami}
|
||||
{test"&whoami} {test"|whoami}
|
||||
{"test"&whoami} {"test"|whoami}
|
||||
{""test"&whoami} {""test"|whoami}
|
||||
|
||||
{test&echo "} {test|echo "}
|
||||
{"test&echo "} {"test|echo "}
|
||||
{test"&echo "} {test"|echo "}
|
||||
{"test"&echo "} {"test"|echo "}
|
||||
{""test"&echo "} {""test"|echo "}
|
||||
|
||||
{test&echo ""} {test|echo ""}
|
||||
{"test&echo ""} {"test|echo ""}
|
||||
{test"&echo ""} {test"|echo ""}
|
||||
{"test"&echo ""} {"test"|echo ""}
|
||||
{""test"&echo ""} {""test"|echo ""}
|
||||
|
||||
{test>whoami} {test<whoami}
|
||||
{"test>whoami} {"test<whoami}
|
||||
{test">whoami} {test"<whoami}
|
||||
{"test">whoami} {"test"<whoami}
|
||||
{""test">whoami} {""test"<whoami}
|
||||
{test(whoami)} {test(whoami)}
|
||||
{test"(whoami)} {test"(whoami)}
|
||||
{test^whoami} {test^^echo ^^^}
|
||||
{test"^whoami} {test"^^echo ^^^}
|
||||
{test"^echo ^^^"} {test""^echo" ^^^"}
|
||||
|
||||
{test%USERDOMAIN%\%USERNAME%}
|
||||
{test" %USERDOMAIN%\%USERNAME%}
|
||||
{test%USERDOMAIN%\\%USERNAME%}
|
||||
{test" %USERDOMAIN%\\%USERNAME%}
|
||||
{test%USERDOMAIN%&%USERNAME%}
|
||||
{test" %USERDOMAIN%&%USERNAME%}
|
||||
{test%USERDOMAIN%\&\%USERNAME%}
|
||||
{test" %USERDOMAIN%\&\%USERNAME%}
|
||||
|
||||
{test%USERDOMAIN%\&\test}
|
||||
{test" %USERDOMAIN%\&\test}
|
||||
{test%USERDOMAIN%\\&\\test}
|
||||
{test" %USERDOMAIN%\\&\\test}
|
||||
|
||||
{test%USERDOMAIN%\&\"test}
|
||||
{test" %USERDOMAIN%\&\"test}
|
||||
{test%USERDOMAIN%\\&\\"test}
|
||||
{test" %USERDOMAIN%\\&\\"test}
|
||||
}
|
||||
|
||||
### validate the pass-thru from BuildCommandLine() to the crt's parse_cmdline().
|
||||
###
|
||||
test winpipe-8.1 {BuildCommandLine/parse_cmdline pass-thru: null arguments} {win exec} {
|
||||
exec [interpreter] $path(echoArgs.tcl) foo "" bar
|
||||
} [list $path(echoArgs.tcl) [list foo {} bar]]
|
||||
test winpipe-8.2 {BuildCommandLine/parse_cmdline pass-thru: null arguments} {win exec} {
|
||||
exec [interpreter] $path(echoArgs.tcl) foo {} bar
|
||||
} [list $path(echoArgs.tcl) [list foo {} bar]]
|
||||
test winpipe-8.3 {BuildCommandLine/parse_cmdline pass-thru: dbl quote quoting #1} {win exec} {
|
||||
exec [interpreter] $path(echoArgs.tcl) foo "\"" bar
|
||||
} [list $path(echoArgs.tcl) [list foo "\"" bar]]
|
||||
test winpipe-8.4 {BuildCommandLine/parse_cmdline pass-thru: dbl quote quoting #2} {win exec} {
|
||||
exec [interpreter] $path(echoArgs.tcl) foo {""} bar
|
||||
} [list $path(echoArgs.tcl) [list foo {""} bar]]
|
||||
test winpipe-8.5 {BuildCommandLine/parse_cmdline pass-thru: dbl quote quoting #3} {win exec} {
|
||||
exec [interpreter] $path(echoArgs.tcl) foo "\" " bar
|
||||
} [list $path(echoArgs.tcl) [list foo "\" " bar]]
|
||||
test winpipe-8.6 {BuildCommandLine/parse_cmdline pass-thru: dbl quote quoting #4} {win exec} {
|
||||
exec [interpreter] $path(echoArgs.tcl) foo {a="b"} bar
|
||||
} [list $path(echoArgs.tcl) [list foo {a="b"} bar]]
|
||||
test winpipe-8.7 {BuildCommandLine/parse_cmdline pass-thru: dbl quote quoting #5} {win exec} {
|
||||
exec [interpreter] $path(echoArgs.tcl) foo {a = "b"} bar
|
||||
} [list $path(echoArgs.tcl) [list foo {a = "b"} bar]]
|
||||
test winpipe-8.8 {BuildCommandLine/parse_cmdline pass-thru: dbl quote quoting #6} {win exec} {
|
||||
exec [interpreter] $path(echoArgs.tcl) {"hello"} {""hello""} {"""hello"""} {"\"hello\""} {he llo} {he " llo}
|
||||
} [list $path(echoArgs.tcl) [list {"hello"} {""hello""} {"""hello"""} {"\"hello\""} {he llo} {he " llo}]]
|
||||
test winpipe-8.9 {BuildCommandLine/parse_cmdline pass-thru: N backslashes followed a quote rule #1} {win exec} {
|
||||
exec [interpreter] $path(echoArgs.tcl) foo \\ bar
|
||||
} [list $path(echoArgs.tcl) [list foo \\ bar]]
|
||||
test winpipe-8.10 {BuildCommandLine/parse_cmdline pass-thru: N backslashes followed a quote rule #2} {win exec} {
|
||||
exec [interpreter] $path(echoArgs.tcl) foo \\\\ bar
|
||||
} [list $path(echoArgs.tcl) [list foo \\\\ bar]]
|
||||
test winpipe-8.11 {BuildCommandLine/parse_cmdline pass-thru: N backslashes followed a quote rule #3} {win exec} {
|
||||
exec [interpreter] $path(echoArgs.tcl) foo \\\ \\ bar
|
||||
} [list $path(echoArgs.tcl) [list foo \\\ \\ bar]]
|
||||
test winpipe-8.12 {BuildCommandLine/parse_cmdline pass-thru: N backslashes followed a quote rule #4} {win exec} {
|
||||
exec [interpreter] $path(echoArgs.tcl) foo \\\ \\\\ bar
|
||||
} [list $path(echoArgs.tcl) [list foo \\\ \\\\ bar]]
|
||||
test winpipe-8.13 {BuildCommandLine/parse_cmdline pass-thru: N backslashes followed a quote rule #5} {win exec} {
|
||||
exec [interpreter] $path(echoArgs.tcl) foo \\\ \\\\\\ bar
|
||||
} [list $path(echoArgs.tcl) [list foo \\\ \\\\\\ bar]]
|
||||
test winpipe-8.14 {BuildCommandLine/parse_cmdline pass-thru: N backslashes followed a quote rule #6} {win exec} {
|
||||
exec [interpreter] $path(echoArgs.tcl) foo \\\ \\\" bar
|
||||
} [list $path(echoArgs.tcl) [list foo \\\ \\\" bar]]
|
||||
test winpipe-8.15 {BuildCommandLine/parse_cmdline pass-thru: N backslashes followed a quote rule #7} {win exec} {
|
||||
exec [interpreter] $path(echoArgs.tcl) foo \\\ \\\\\" bar
|
||||
} [list $path(echoArgs.tcl) [list foo \\\ \\\\\" bar]]
|
||||
test winpipe-8.16 {BuildCommandLine/parse_cmdline pass-thru: N backslashes followed a quote rule #8} {win exec} {
|
||||
exec [interpreter] $path(echoArgs.tcl) foo \\\ \\\\\\\" bar
|
||||
} [list $path(echoArgs.tcl) [list foo \\\ \\\\\\\" bar]]
|
||||
test winpipe-8.17 {BuildCommandLine/parse_cmdline pass-thru: special chars #1} {win exec} {
|
||||
exec [interpreter] $path(echoArgs.tcl) foo \{ bar
|
||||
} [list $path(echoArgs.tcl) [list foo \{ bar]]
|
||||
test winpipe-8.18 {BuildCommandLine/parse_cmdline pass-thru: special chars #2} {win exec} {
|
||||
exec [interpreter] $path(echoArgs.tcl) foo \} bar
|
||||
} [list $path(echoArgs.tcl) [list foo \} bar]]
|
||||
test winpipe-8.19 {ensure parse_cmdline isn't doing wildcard replacement} {win exec} {
|
||||
exec [interpreter] $path(echoArgs.tcl) foo * makefile.?c bar
|
||||
} [list $path(echoArgs.tcl) [list foo * makefile.?c bar]]
|
||||
test winpipe-8.1 {BuildCommandLine/parse_cmdline pass-thru: dumped arguments are equal original} \
|
||||
-constraints {win exec} -body {
|
||||
_testExecArgs 0 \
|
||||
[list foo "" bar] \
|
||||
[list foo {} bar] \
|
||||
[list foo "\"" bar] \
|
||||
[list foo {""} bar] \
|
||||
[list foo "\" " bar] \
|
||||
[list foo {a="b"} bar] \
|
||||
[list foo {a = "b"} bar] \
|
||||
[list {"hello"} {""hello""} {"""hello"""} {"\"hello\""} {he llo} {he " llo}] \
|
||||
[list foo \\ bar] \
|
||||
[list foo \\\\ bar] \
|
||||
[list foo \\\ \\ bar] \
|
||||
[list foo \\\ \\\\ bar] \
|
||||
[list foo \\\ \\\\\\ bar] \
|
||||
[list foo \\\ \\\" bar] \
|
||||
[list foo \\\ \\\\\" bar] \
|
||||
[list foo \\\ \\\\\\\" bar] \
|
||||
[list foo \{ bar] \
|
||||
[list foo \} bar] \
|
||||
[list foo * makefile.?c bar]
|
||||
} -result {}
|
||||
|
||||
test winpipe-8.2 {BuildCommandLine/parse_cmdline pass-thru: check injection on special meta-chars (particular)} \
|
||||
-constraints {win exec slowTest} -body {
|
||||
_testExecArgs 1 {*}$injectList
|
||||
} -result {}
|
||||
|
||||
test winpipe-8.3 {BuildCommandLine/parse_cmdline pass-thru: check injection on special meta-chars (jointly)} \
|
||||
-constraints {win exec} -body {
|
||||
_testExecArgs 0 \
|
||||
[list START {*}$injectList END] \
|
||||
[list "START\"" {*}$injectList END] \
|
||||
[list START {*}$injectList "\"END"] \
|
||||
[list "START\"" {*}$injectList "\"END"]
|
||||
} -result {}
|
||||
|
||||
test winpipe-8.4 {BuildCommandLine/parse_cmdline pass-thru: check injection on special meta-chars (command/jointly args)} \
|
||||
-constraints {win exec} -body {
|
||||
_testExecArgs 2 \
|
||||
[list START {*}$injectList END] \
|
||||
[list "START\"" {*}$injectList END] \
|
||||
[list START {*}$injectList "\"END"] \
|
||||
[list "START\"" {*}$injectList "\"END"]
|
||||
} -result {}
|
||||
|
||||
test winpipe-8.5 {BuildCommandLine/parse_cmdline pass-thru: check injection on special meta-chars (random mix)} \
|
||||
-constraints {win exec} -body {
|
||||
set lst {}
|
||||
set maps {
|
||||
{\&|^<>!()%}
|
||||
{\&|^<>!()% }
|
||||
{"\&|^<>!()%}
|
||||
{"\&|^<>!()% }
|
||||
{"""""\\\\\&|^<>!()%}
|
||||
{"""""\\\\\&|^<>!()% }
|
||||
}
|
||||
set i 0
|
||||
time {
|
||||
set args {[incr i].}
|
||||
time {
|
||||
set map [lindex $maps [expr {int(rand()*[llength $maps])}]]
|
||||
# be sure arg has some prefix (avoid special handling, like |& etc)
|
||||
set a {x}
|
||||
while {[string length $a] < 50} {
|
||||
append a [string index $map [expr {int(rand()*[string length $map])}]]
|
||||
}
|
||||
lappend args $a
|
||||
} 20
|
||||
lappend lst $args
|
||||
} 10
|
||||
_testExecArgs 0 {*}$lst
|
||||
} -result {} -cleanup {
|
||||
unset -nocomplain lst args a map maps
|
||||
}
|
||||
|
||||
set injectList {
|
||||
"test\"\nwhoami" "test\"\"\nwhoami"
|
||||
"test\"\"\"\nwhoami" "test\"\"\"\"\nwhoami"
|
||||
"test;\n&echo \"" "\"test;\n&echo \""
|
||||
"test\";\n&echo \"" "\"test\";\n&echo \""
|
||||
"\"\"test\";\n&echo \""
|
||||
}
|
||||
|
||||
test winpipe-8.6 {BuildCommandLine/parse_cmdline pass-thru: check new-line quoted in args} \
|
||||
-constraints {win exec} -body {
|
||||
# test exe only, because currently there is no proper way to escape a new-line char resp.
|
||||
# to supply a new-line to the batch-files within arguments (command line is truncated).
|
||||
_testExecArgs 8 \
|
||||
[list START {*}$injectList END] \
|
||||
[list "START\"" {*}$injectList END] \
|
||||
[list START {*}$injectList "\"END"] \
|
||||
[list "START\"" {*}$injectList "\"END"]
|
||||
} -result {}
|
||||
|
||||
test winpipe-8.7 {BuildCommandLine/parse_cmdline pass-thru: check new-line quoted in args (batch)} \
|
||||
-constraints {win exec knownBug} -body {
|
||||
# this will fail if executed batch-file, because currently there is no proper way to escape a new-line char.
|
||||
_testExecArgs 0 $injectList
|
||||
} -result {}
|
||||
|
||||
|
||||
rename _testExecArgs {}
|
||||
|
||||
# restore old values for env(TMP) and env(TEMP)
|
||||
|
||||
@@ -445,7 +604,9 @@ removeFile more
|
||||
removeFile stdout
|
||||
removeFile stderr
|
||||
removeFile nothing
|
||||
removeFile echoArgs.tcl
|
||||
if {[info exists path(echoArgs.tcl)]} { removeFile echoArgs.tcl }
|
||||
if {[info exists path(echoArgs.bat)]} { removeFile echoArgs.bat }
|
||||
if {[info exists path(echoArgs2.bat)]} { removeDirectory test(Dir)Check }
|
||||
::tcltest::cleanupTests
|
||||
return
|
||||
|
||||
|
||||
+31
-8
@@ -191,12 +191,21 @@ proc genStubs::declare {args} {
|
||||
regsub -all "\[ \t\n\]+" [string trim $decl] " " decl
|
||||
set decl [parseDecl $decl]
|
||||
|
||||
foreach platform $platformList {
|
||||
if {$decl ne ""} {
|
||||
set stubs($curName,$platform,$index) $decl
|
||||
if {![info exists stubs($curName,$platform,lastNum)] \
|
||||
|| ($index > $stubs($curName,$platform,lastNum))} {
|
||||
set stubs($curName,$platform,lastNum) $index
|
||||
if {([lindex $platformList 0] eq "deprecated")} {
|
||||
set stubs($curName,deprecated,$index) [lindex $platformList 1]
|
||||
set stubs($curName,generic,$index) $decl
|
||||
if {![info exists stubs($curName,generic,lastNum)] \
|
||||
|| ($index > $stubs($curName,generic,lastNum))} {
|
||||
set stubs($curName,generic,lastNum) $index
|
||||
}
|
||||
} else {
|
||||
foreach platform $platformList {
|
||||
if {$decl ne ""} {
|
||||
set stubs($curName,$platform,$index) $decl
|
||||
if {![info exists stubs($curName,$platform,lastNum)] \
|
||||
|| ($index > $stubs($curName,$platform,lastNum))} {
|
||||
set stubs($curName,$platform,lastNum) $index
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -455,10 +464,17 @@ proc genStubs::parseArg {arg} {
|
||||
|
||||
proc genStubs::makeDecl {name decl index} {
|
||||
variable scspec
|
||||
variable stubs
|
||||
variable libraryName
|
||||
lassign $decl rtype fname args
|
||||
|
||||
append text "/* $index */\n"
|
||||
set line "$scspec $rtype"
|
||||
if {[info exists stubs($name,deprecated,$index)]} {
|
||||
append text "[string toupper $libraryName]_DEPRECATED(\"$stubs($name,deprecated,$index)\")\n"
|
||||
set line "$rtype"
|
||||
} else {
|
||||
set line "$scspec $rtype"
|
||||
}
|
||||
set count [expr {2 - ([string length $line] / 8)}]
|
||||
append line [string range "\t\t\t" 0 $count]
|
||||
set pad [expr {24 - [string length $line]}]
|
||||
@@ -569,11 +585,15 @@ proc genStubs::makeMacro {name decl index} {
|
||||
|
||||
proc genStubs::makeSlot {name decl index} {
|
||||
lassign $decl rtype fname args
|
||||
variable stubs
|
||||
|
||||
set lfname [string tolower [string index $fname 0]]
|
||||
append lfname [string range $fname 1 end]
|
||||
|
||||
set text " "
|
||||
if {[info exists stubs($name,deprecated,$index)]} {
|
||||
append text "TCL_DEPRECATED_API(\"$stubs($name,deprecated,$index)\") "
|
||||
}
|
||||
if {$args eq ""} {
|
||||
append text $rtype " *" $lfname "; /* $index */\n"
|
||||
return $text
|
||||
@@ -682,7 +702,10 @@ proc genStubs::forAllStubs {name slotProc onAll textVar
|
||||
for {set i 0} {$i <= $lastNum} {incr i} {
|
||||
set slots [array names stubs $name,*,$i]
|
||||
set emit 0
|
||||
if {[info exists stubs($name,generic,$i)]} {
|
||||
if {[info exists stubs($name,deprecated,$i)]} {
|
||||
append text [$slotProc $name $stubs($name,generic,$i) $i]
|
||||
set emit 1
|
||||
} elseif {[info exists stubs($name,generic,$i)]} {
|
||||
if {[llength $slots] > 1} {
|
||||
puts stderr "conflicting generic and platform entries:\
|
||||
$name $i"
|
||||
|
||||
+2
-2
@@ -842,8 +842,8 @@ install-libraries: libraries
|
||||
do \
|
||||
$(INSTALL_DATA) $$i "$(SCRIPT_INSTALL_DIR)"/http1.0; \
|
||||
done;
|
||||
@echo "Installing package http 2.8.13 as a Tcl Module";
|
||||
@$(INSTALL_DATA) $(TOP_DIR)/library/http/http.tcl "$(SCRIPT_INSTALL_DIR)"/../tcl8/8.6/http-2.8.13.tm;
|
||||
@echo "Installing package http 2.9.0 as a Tcl Module";
|
||||
@$(INSTALL_DATA) $(TOP_DIR)/library/http/http.tcl "$(SCRIPT_INSTALL_DIR)"/../tcl8/8.6/http-2.9.0.tm;
|
||||
@echo "Installing package opt0.4 files to $(SCRIPT_INSTALL_DIR)/opt0.4/";
|
||||
@for i in $(TOP_DIR)/library/opt/*.tcl ; \
|
||||
do \
|
||||
|
||||
+2
-2
@@ -651,8 +651,8 @@ install-libraries: libraries install-tzdata install-msgs
|
||||
do \
|
||||
$(COPY) "$$j" "$(SCRIPT_INSTALL_DIR)/http1.0"; \
|
||||
done;
|
||||
@echo "Installing package http 2.8.13 as a Tcl Module";
|
||||
@$(COPY) $(ROOT_DIR)/library/http/http.tcl $(SCRIPT_INSTALL_DIR)/../tcl8/8.6/http-2.8.13.tm;
|
||||
@echo "Installing package http 2.9.0 as a Tcl Module";
|
||||
@$(COPY) $(ROOT_DIR)/library/http/http.tcl $(SCRIPT_INSTALL_DIR)/../tcl8/8.6/http-2.9.0.tm;
|
||||
@echo "Installing library opt0.4 directory";
|
||||
@for j in $(ROOT_DIR)/library/opt/*.tcl; \
|
||||
do \
|
||||
|
||||
+3
-1
@@ -35,8 +35,10 @@ extern Tcl_PackageInitProc Dde_Init;
|
||||
extern Tcl_PackageInitProc Dde_SafeInit;
|
||||
#endif
|
||||
|
||||
#ifdef TCL_BROKEN_MAINARGS
|
||||
#if defined(__GNUC__) || defined(TCL_BROKEN_MAINARGS)
|
||||
int _CRT_glob = 0;
|
||||
#endif /* __GNUC__ || TCL_BROKEN_MAINARGS */
|
||||
#ifdef TCL_BROKEN_MAINARGS
|
||||
static void setargv(int *argcPtr, TCHAR ***argvPtr);
|
||||
#endif /* TCL_BROKEN_MAINARGS */
|
||||
|
||||
|
||||
+12
-51
@@ -33,10 +33,6 @@ static int platformId; /* Running under NT, or 95/98? */
|
||||
#define cpuid __asm __emit 0fh __asm __emit 0a2h
|
||||
#endif
|
||||
|
||||
#if TCL_UTF_MAX < 4
|
||||
static Tcl_Encoding winTCharEncoding = NULL;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* The following declaration is for the VC++ DLL entry point.
|
||||
*/
|
||||
@@ -201,8 +197,6 @@ TclWinInit(
|
||||
if (platformId == VER_PLATFORM_WIN32_WINDOWS) {
|
||||
Tcl_Panic("Windows 9x is not a supported platform");
|
||||
}
|
||||
|
||||
TclWinResetInterfaces();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -282,10 +276,6 @@ TclWinNoBackslash(
|
||||
void
|
||||
TclpSetInterfaces(void)
|
||||
{
|
||||
#if TCL_UTF_MAX < 4
|
||||
TclWinResetInterfaces();
|
||||
winTCharEncoding = Tcl_GetEncoding(NULL, "unicode");
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -313,8 +303,6 @@ TclWinEncodingsCleanup(void)
|
||||
{
|
||||
MountPointMap *dlIter, *dlIter2;
|
||||
|
||||
TclWinResetInterfaces();
|
||||
|
||||
/*
|
||||
* Clean up the mount point map.
|
||||
*/
|
||||
@@ -348,12 +336,6 @@ TclWinEncodingsCleanup(void)
|
||||
void
|
||||
TclWinResetInterfaces(void)
|
||||
{
|
||||
#if TCL_UTF_MAX < 4
|
||||
if (winTCharEncoding != NULL) {
|
||||
Tcl_FreeEncoding(winTCharEncoding);
|
||||
winTCharEncoding = NULL;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -565,22 +547,11 @@ Tcl_WinUtfToTChar(
|
||||
Tcl_DString *dsPtr) /* Uninitialized or free DString in which the
|
||||
* converted string is stored. */
|
||||
{
|
||||
#if TCL_UTF_MAX > 3
|
||||
TCHAR *wp;
|
||||
int size = MultiByteToWideChar(CP_UTF8, 0, string, len, 0, 0);
|
||||
|
||||
Tcl_DStringInit(dsPtr);
|
||||
Tcl_DStringSetLength(dsPtr, 2*size+2);
|
||||
wp = (TCHAR *)Tcl_DStringValue(dsPtr);
|
||||
MultiByteToWideChar(CP_UTF8, 0, string, len, wp, size+1);
|
||||
if (len == -1) --size; /* account for 0-byte at string end */
|
||||
Tcl_DStringSetLength(dsPtr, 2*size);
|
||||
wp[size] = 0;
|
||||
return wp;
|
||||
#else
|
||||
return (TCHAR *) Tcl_UtfToExternalDString(winTCharEncoding,
|
||||
string, len, dsPtr);
|
||||
#endif
|
||||
if (!string) {
|
||||
return NULL;
|
||||
}
|
||||
return Tcl_UtfToUniCharDString(string, len, dsPtr);
|
||||
}
|
||||
|
||||
char *
|
||||
@@ -591,26 +562,16 @@ Tcl_WinTCharToUtf(
|
||||
Tcl_DString *dsPtr) /* Uninitialized or free DString in which the
|
||||
* converted string is stored. */
|
||||
{
|
||||
#if TCL_UTF_MAX > 3
|
||||
char *p;
|
||||
int size;
|
||||
|
||||
if (len > 0) {
|
||||
Tcl_DStringInit(dsPtr);
|
||||
if (!string) {
|
||||
return NULL;
|
||||
}
|
||||
if (len < 0) {
|
||||
len = wcslen(string);
|
||||
} else {
|
||||
len /= 2;
|
||||
}
|
||||
size = WideCharToMultiByte(CP_UTF8, 0, string, len, 0, 0, NULL, NULL);
|
||||
Tcl_DStringInit(dsPtr);
|
||||
Tcl_DStringSetLength(dsPtr, size+1);
|
||||
p = (char *)Tcl_DStringValue(dsPtr);
|
||||
WideCharToMultiByte(CP_UTF8, 0, string, len, p, size, NULL, NULL);
|
||||
if (len == -1) --size; /* account for 0-byte at string end */
|
||||
Tcl_DStringSetLength(dsPtr, size);
|
||||
p[size] = 0;
|
||||
return p;
|
||||
#else
|
||||
return Tcl_ExternalToUtfDString(winTCharEncoding,
|
||||
(const char *) string, len, dsPtr);
|
||||
#endif
|
||||
return Tcl_UniCharToUtfDString(string, len, dsPtr);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
+3
-4
@@ -1026,7 +1026,8 @@ DoRemoveJustDirectory(
|
||||
|
||||
if (nativePath == NULL || nativePath[0] == '\0') {
|
||||
Tcl_SetErrno(ENOENT);
|
||||
goto end;
|
||||
Tcl_DStringInit(errorPtr);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
attr = GetFileAttributes(nativePath);
|
||||
@@ -1108,9 +1109,7 @@ DoRemoveJustDirectory(
|
||||
|
||||
end:
|
||||
if (errorPtr != NULL) {
|
||||
char *p;
|
||||
Tcl_WinTCharToUtf(nativePath, -1, errorPtr);
|
||||
p = Tcl_DStringValue(errorPtr);
|
||||
char *p = Tcl_WinTCharToUtf(nativePath, -1, errorPtr);
|
||||
for (; *p; ++p) {
|
||||
if (*p == '\\') *p = '/';
|
||||
}
|
||||
|
||||
+176
-47
@@ -1289,7 +1289,7 @@ ApplicationType(
|
||||
|
||||
/*
|
||||
* Look for the program as an external program. First try the name as it
|
||||
* is, then try adding .com, .exe, and .bat, in that order, to the name,
|
||||
* is, then try adding .com, .exe, .bat and .cmd, in that order, to the name,
|
||||
* looking for an executable.
|
||||
*
|
||||
* Using the raw SearchPath() function doesn't do quite what is necessary.
|
||||
@@ -1408,7 +1408,7 @@ ApplicationType(
|
||||
return APPL_NONE;
|
||||
}
|
||||
|
||||
if ((applType == APPL_DOS) || (applType == APPL_WIN3X)) {
|
||||
if (applType == APPL_WIN3X) {
|
||||
/*
|
||||
* Replace long path name of executable with short path name for
|
||||
* 16-bit applications. Otherwise the application may not be able to
|
||||
@@ -1442,6 +1442,86 @@ ApplicationType(
|
||||
*----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
static const char *
|
||||
BuildCmdLineBypassBS(
|
||||
const char *current,
|
||||
const char **bspos
|
||||
) {
|
||||
/* mark first backslash possition */
|
||||
if (!*bspos) {
|
||||
*bspos = current;
|
||||
}
|
||||
do {
|
||||
current++;
|
||||
} while (*current == '\\');
|
||||
return current;
|
||||
}
|
||||
|
||||
static void
|
||||
QuoteCmdLineBackslash(
|
||||
Tcl_DString *dsPtr,
|
||||
const char *start,
|
||||
const char *current,
|
||||
const char *bspos
|
||||
) {
|
||||
if (!bspos) {
|
||||
if (current > start) { /* part before current (special) */
|
||||
Tcl_DStringAppend(dsPtr, start, (int) (current - start));
|
||||
}
|
||||
} else {
|
||||
if (bspos > start) { /* part before first backslash */
|
||||
Tcl_DStringAppend(dsPtr, start, (int) (bspos - start));
|
||||
}
|
||||
while (bspos++ < current) { /* each backslash twice */
|
||||
TclDStringAppendLiteral(dsPtr, "\\\\");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const char *
|
||||
QuoteCmdLinePart(
|
||||
Tcl_DString *dsPtr,
|
||||
const char *start,
|
||||
const char *special,
|
||||
const char *specMetaChars,
|
||||
const char **bspos
|
||||
) {
|
||||
if (!*bspos) {
|
||||
/* rest before special (before quote) */
|
||||
QuoteCmdLineBackslash(dsPtr, start, special, NULL);
|
||||
start = special;
|
||||
} else {
|
||||
/* rest before first backslash and backslashes into new quoted block */
|
||||
QuoteCmdLineBackslash(dsPtr, start, *bspos, NULL);
|
||||
start = *bspos;
|
||||
}
|
||||
/*
|
||||
* escape all special chars enclosed in quotes like `"..."`, note that here we
|
||||
* don't must escape `\` (with `\`), because it's outside of the main quotes,
|
||||
* so `\` remains `\`, but important - not at end of part, because results as
|
||||
* before the quote, so `%\%\` should be escaped as `"%\%"\\`).
|
||||
*/
|
||||
TclDStringAppendLiteral(dsPtr, "\""); /* opening escape quote-char */
|
||||
do {
|
||||
*bspos = NULL;
|
||||
special++;
|
||||
if (*special == '\\') {
|
||||
/* bypass backslashes (and mark first backslash possition)*/
|
||||
special = BuildCmdLineBypassBS(special, bspos);
|
||||
if (*special == '\0') break;
|
||||
}
|
||||
} while (*special && strchr(specMetaChars, *special));
|
||||
if (!*bspos) {
|
||||
/* unescaped rest before quote */
|
||||
QuoteCmdLineBackslash(dsPtr, start, special, NULL);
|
||||
} else {
|
||||
/* unescaped rest before first backslash (rather belongs to the main block) */
|
||||
QuoteCmdLineBackslash(dsPtr, start, *bspos, NULL);
|
||||
}
|
||||
TclDStringAppendLiteral(dsPtr, "\""); /* closing escape quote-char */
|
||||
return special;
|
||||
}
|
||||
|
||||
static void
|
||||
BuildCommandLine(
|
||||
const char *executable, /* Full path of executable (including
|
||||
@@ -1451,10 +1531,22 @@ BuildCommandLine(
|
||||
Tcl_DString *linePtr) /* Initialized Tcl_DString that receives the
|
||||
* command line (TCHAR). */
|
||||
{
|
||||
const char *arg, *start, *special;
|
||||
int quote, i;
|
||||
const char *arg, *start, *special, *bspos;
|
||||
int quote = 0, i;
|
||||
Tcl_DString ds;
|
||||
|
||||
/* characters to enclose in quotes if unpaired quote flag set */
|
||||
const static char *specMetaChars = "&|^<>!()%";
|
||||
/* characters to enclose in quotes in any case (regardless unpaired-flag) */
|
||||
const static char *specMetaChars2 = "%";
|
||||
|
||||
/* Quote flags:
|
||||
* CL_ESCAPE - escape argument;
|
||||
* CL_QUOTE - enclose in quotes;
|
||||
* CL_UNPAIRED - previous arguments chain contains unpaired quote-char;
|
||||
*/
|
||||
enum {CL_ESCAPE = 1, CL_QUOTE = 2, CL_UNPAIRED = 4};
|
||||
|
||||
Tcl_DStringInit(&ds);
|
||||
|
||||
/*
|
||||
@@ -1474,61 +1566,98 @@ BuildCommandLine(
|
||||
TclDStringAppendLiteral(&ds, " ");
|
||||
}
|
||||
|
||||
quote = 0;
|
||||
quote &= ~(CL_ESCAPE|CL_QUOTE); /* reset escape flags */
|
||||
bspos = NULL;
|
||||
if (arg[0] == '\0') {
|
||||
quote = 1;
|
||||
quote = CL_QUOTE;
|
||||
} else {
|
||||
int count;
|
||||
Tcl_UniChar ch = 0;
|
||||
|
||||
for (start = arg; *start != '\0'; start += count) {
|
||||
count = TclUtfToUniChar(start, &ch);
|
||||
if (Tcl_UniCharIsSpace(ch)) { /* INTL: ISO space. */
|
||||
quote = 1;
|
||||
Tcl_UniChar ch;
|
||||
for (start = arg;
|
||||
*start != '\0' &&
|
||||
(quote & (CL_ESCAPE|CL_QUOTE)) != (CL_ESCAPE|CL_QUOTE);
|
||||
start += count
|
||||
) {
|
||||
count = Tcl_UtfToUniChar(start, &ch);
|
||||
if (count > 1) continue;
|
||||
if (Tcl_UniCharIsSpace(ch)) {
|
||||
quote |= CL_QUOTE; /* quote only */
|
||||
if (bspos) { /* if backslash found - escape & quote */
|
||||
quote |= CL_ESCAPE;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (strchr(specMetaChars, *start)) {
|
||||
quote |= (CL_ESCAPE|CL_QUOTE); /*escape & quote */
|
||||
break;
|
||||
}
|
||||
if (*start == '"') {
|
||||
quote |= CL_ESCAPE; /* escape only */
|
||||
continue;
|
||||
}
|
||||
if (*start == '\\') {
|
||||
bspos = start;
|
||||
if (quote & CL_QUOTE) { /* if quote - escape & quote */
|
||||
quote |= CL_ESCAPE;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
bspos = NULL;
|
||||
}
|
||||
if (quote) {
|
||||
if (quote & CL_QUOTE) {
|
||||
/* start of argument (main opening quote-char) */
|
||||
TclDStringAppendLiteral(&ds, "\"");
|
||||
}
|
||||
start = arg;
|
||||
for (special = arg; ; ) {
|
||||
if ((*special == '\\') && (special[1] == '\\' ||
|
||||
special[1] == '"' || (quote && special[1] == '\0'))) {
|
||||
Tcl_DStringAppend(&ds, start, (int) (special - start));
|
||||
start = special;
|
||||
while (1) {
|
||||
special++;
|
||||
if (*special == '"' || (quote && *special == '\0')) {
|
||||
/*
|
||||
* N backslashes followed a quote -> insert N * 2 + 1
|
||||
* backslashes then a quote.
|
||||
*/
|
||||
|
||||
Tcl_DStringAppend(&ds, start,
|
||||
(int) (special - start));
|
||||
break;
|
||||
}
|
||||
if (*special != '\\') {
|
||||
break;
|
||||
}
|
||||
if (!(quote & CL_ESCAPE)) {
|
||||
/* nothing to escape */
|
||||
Tcl_DStringAppend(&ds, arg, -1);
|
||||
} else {
|
||||
start = arg;
|
||||
for (special = arg; *special != '\0'; ) {
|
||||
/* position of `\` is important before quote or at end (equal `\"` because quoted) */
|
||||
if (*special == '\\') {
|
||||
/* bypass backslashes (and mark first backslash possition)*/
|
||||
special = BuildCmdLineBypassBS(special, &bspos);
|
||||
if (*special == '\0') break;
|
||||
}
|
||||
Tcl_DStringAppend(&ds, start, (int) (special - start));
|
||||
start = special;
|
||||
/* ["] */
|
||||
if (*special == '"') {
|
||||
quote ^= CL_UNPAIRED; /* invert unpaired flag - observe unpaired quotes */
|
||||
/* add part before (and escape backslashes before quote) */
|
||||
QuoteCmdLineBackslash(&ds, start, special, bspos);
|
||||
bspos = NULL;
|
||||
/* escape using backslash */
|
||||
TclDStringAppendLiteral(&ds, "\\\"");
|
||||
start = ++special;
|
||||
continue;
|
||||
}
|
||||
/* unpaired (escaped) quote causes special handling on meta-chars */
|
||||
if ((quote & CL_UNPAIRED) && strchr(specMetaChars, *special)) {
|
||||
special = QuoteCmdLinePart(&ds, start, special, specMetaChars, &bspos);
|
||||
/* start to current or first backslash */
|
||||
start = !bspos ? special : bspos;
|
||||
continue;
|
||||
}
|
||||
/* special case for % - should be enclosed always (paired also) */
|
||||
if (strchr(specMetaChars2, *special)) {
|
||||
special = QuoteCmdLinePart(&ds, start, special, specMetaChars2, &bspos);
|
||||
/* start to current or first backslash */
|
||||
start = !bspos ? special : bspos;
|
||||
continue;
|
||||
}
|
||||
/* other not special (and not meta) character */
|
||||
bspos = NULL; /* reset last backslash possition (not interesting) */
|
||||
special++;
|
||||
}
|
||||
if (*special == '"') {
|
||||
Tcl_DStringAppend(&ds, start, (int) (special - start));
|
||||
TclDStringAppendLiteral(&ds, "\\\"");
|
||||
start = special + 1;
|
||||
}
|
||||
if (*special == '\0') {
|
||||
break;
|
||||
}
|
||||
special++;
|
||||
/* rest of argument (and escape backslashes before closing main quote) */
|
||||
QuoteCmdLineBackslash(&ds, start, special,
|
||||
(quote & CL_QUOTE) ? bspos : NULL);
|
||||
}
|
||||
Tcl_DStringAppend(&ds, start, (int) (special - start));
|
||||
if (quote) {
|
||||
if (quote & CL_QUOTE) {
|
||||
/* end of argument (main closing quote-char) */
|
||||
TclDStringAppendLiteral(&ds, "\"");
|
||||
}
|
||||
}
|
||||
|
||||
+15
-19
@@ -22,13 +22,6 @@
|
||||
#endif
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifndef UNICODE
|
||||
# undef Tcl_WinTCharToUtf
|
||||
# define Tcl_WinTCharToUtf(a,b,c) Tcl_ExternalToUtfDString(NULL,a,b,c)
|
||||
# undef Tcl_WinUtfToTChar
|
||||
# define Tcl_WinUtfToTChar(a,b,c) Tcl_UtfToExternalDString(NULL,a,b,c)
|
||||
#endif /* !UNICODE */
|
||||
|
||||
/*
|
||||
* Ensure that we can say which registry is being accessed.
|
||||
*/
|
||||
@@ -163,7 +156,7 @@ Registry_Init(
|
||||
cmd = Tcl_CreateObjCommand(interp, "registry", RegistryObjCmd,
|
||||
interp, DeleteCmd);
|
||||
Tcl_SetAssocData(interp, REGISTRY_ASSOC_KEY, NULL, cmd);
|
||||
return Tcl_PkgProvide(interp, "registry", "1.3.2");
|
||||
return Tcl_PkgProvide(interp, "registry", "1.3.3");
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -414,12 +407,12 @@ DeleteKey(
|
||||
*/
|
||||
|
||||
keyName = Tcl_GetString(keyNameObj);
|
||||
buffer = ckalloc(keyNameObj->length + 1);
|
||||
buffer = Tcl_Alloc(keyNameObj->length + 1);
|
||||
strcpy(buffer, keyName);
|
||||
|
||||
if (ParseKeyName(interp, buffer, &hostName, &rootKey,
|
||||
&keyName) != TCL_OK) {
|
||||
ckfree(buffer);
|
||||
Tcl_Free(buffer);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
@@ -427,7 +420,7 @@ DeleteKey(
|
||||
Tcl_SetObjResult(interp,
|
||||
Tcl_NewStringObj("bad key: cannot delete root keys", -1));
|
||||
Tcl_SetErrorCode(interp, "WIN_REG", "DEL_ROOT_KEY", NULL);
|
||||
ckfree(buffer);
|
||||
Tcl_Free(buffer);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
@@ -442,7 +435,7 @@ DeleteKey(
|
||||
mode |= KEY_ENUMERATE_SUB_KEYS | DELETE;
|
||||
result = OpenSubKey(hostName, rootKey, keyName, mode, 0, &subkey);
|
||||
if (result != ERROR_SUCCESS) {
|
||||
ckfree(buffer);
|
||||
Tcl_Free(buffer);
|
||||
if (result == ERROR_FILE_NOT_FOUND) {
|
||||
return TCL_OK;
|
||||
}
|
||||
@@ -470,7 +463,7 @@ DeleteKey(
|
||||
}
|
||||
|
||||
RegCloseKey(subkey);
|
||||
ckfree(buffer);
|
||||
Tcl_Free(buffer);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -603,8 +596,7 @@ GetKeyNames(
|
||||
}
|
||||
break;
|
||||
}
|
||||
Tcl_WinTCharToUtf(buffer, bufSize * sizeof(TCHAR), &ds);
|
||||
name = Tcl_DStringValue(&ds);
|
||||
name = Tcl_WinTCharToUtf(buffer, bufSize * sizeof(TCHAR), &ds);
|
||||
if (pattern && !Tcl_StringMatch(name, pattern)) {
|
||||
Tcl_DStringFree(&ds);
|
||||
continue;
|
||||
@@ -950,7 +942,7 @@ OpenKey(
|
||||
|
||||
keyName = Tcl_GetString(keyNameObj);
|
||||
length = keyNameObj->length;
|
||||
buffer = ckalloc(length + 1);
|
||||
buffer = Tcl_Alloc(length + 1);
|
||||
strcpy(buffer, keyName);
|
||||
|
||||
result = ParseKeyName(interp, buffer, &hostName, &rootKey, &keyName);
|
||||
@@ -966,7 +958,7 @@ OpenKey(
|
||||
}
|
||||
}
|
||||
|
||||
ckfree(buffer);
|
||||
Tcl_Free(buffer);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1019,7 +1011,9 @@ OpenSubKey(
|
||||
* this key must be closed by the caller.
|
||||
*/
|
||||
|
||||
keyName = (char *) Tcl_WinUtfToTChar(keyName, -1, &buf);
|
||||
if (keyName) {
|
||||
keyName = (char *) Tcl_WinUtfToTChar(keyName, -1, &buf);
|
||||
}
|
||||
if (flags & REG_CREATE) {
|
||||
DWORD create;
|
||||
|
||||
@@ -1037,7 +1031,9 @@ OpenSubKey(
|
||||
result = RegOpenKeyEx(rootKey, (TCHAR *)keyName, 0, mode,
|
||||
keyPtr);
|
||||
}
|
||||
Tcl_DStringFree(&buf);
|
||||
if (keyName) {
|
||||
Tcl_DStringFree(&buf);
|
||||
}
|
||||
|
||||
/*
|
||||
* Be sure to close the root key since we are done with it now.
|
||||
|
||||
Reference in New Issue
Block a user