Compare commits

...

62 Commits

Author SHA1 Message Date
43f6fd5060 install msi for all "Command" builds
Some checks failed
Check EditorConfig / editorconfig (push) Failing after 2s
Base / Cross linux-aarch64 (push) Failing after 1s
Base / Cross linux-arm gnueabi (push) Failing after 2s
Base / Cross linux-arm gnueabihf (push) Failing after 1s
Base / CentOS-7 (push) Failing after 2s
Base / Fedora-33 (push) Failing after 1s
Base / Fedora-latest (push) Failing after 2s
Base / Ub-20 clang-10 C++11 (push) Has been cancelled
Base / MacOS clang-12 (push) Has been cancelled
Base / Ub-20 clang-10 (push) Has been cancelled
Base / Ub-20 gcc-9 + RT-5.1 beatnik (push) Has been cancelled
Base / Ub-20 gcc-9 + RT-4.10 (push) Has been cancelled
Base / Ub-20 gcc-9 + RT-4.9 (push) Has been cancelled
Base / Ub-20 gcc-9 + RT-5.1 pc686 (push) Has been cancelled
Base / Ub-20 gcc-9 + RT-5.1 uC5282 (push) Has been cancelled
Base / Ub-20 gcc-9 + RT-5.1 xilinx_zynq_a9_qemu (push) Has been cancelled
Base / Win2019 mingw (push) Has been cancelled
Base / Ub-20 gcc-9 + MinGW (push) Has been cancelled
Base / Ub-20 gcc-9 unsigned char (push) Has been cancelled
Base / Ub-20 gcc-9 C++11, static (push) Has been cancelled
Base / Ub-20 gcc-9 + MinGW, static (push) Has been cancelled
Base / Win2019 MSC-19 (push) Has been cancelled
Base / Win2019 MSC-19, debug (push) Has been cancelled
Base / Win2019 MSC-19, static (push) Has been cancelled
2023-10-03 16:50:39 +02:00
Michael Davidsaver
c8d596034a add missing include 2023-09-01 16:11:50 +02:00
Emilio Perez
1d3459982c Update release notes regarding adding error symbols 2023-09-01 16:11:50 +02:00
Emilio Perez
9934242ccf Add locking to error symbol table
This protects the cases of:
- simultaneously adding and requesting of an error symbol
- simultaneously adding many error symbols

x
2023-09-01 16:11:50 +02:00
Emilio Perez
e6dab90bf4 Make adding an identical error symbol not fail
A test case was also added which test that adding an error symbol
with same error code and message as one added before will not fail
2023-09-01 16:11:50 +02:00
Emilio Perez
c680b9bebd error: add a constant for the minimum module number 2023-09-01 16:11:50 +02:00
Emilio Perez
9555ca05b8 error: statically allocate error symbol hash table
This will allow calling errSymbolAdd before errSymBld, therefore, a
function adding error symbols can now be run before iocInit or errlogInit
2023-09-01 16:11:49 +02:00
Emilio Perez
0cd56fa40b Allow adding error symbols after early initialization
This was acomplished by making errSymbolAdd add the error symbol directly
into the global hash table and removing errnumlist which is not needed
anymore.

Unit tests were added for checking the following cases:
- Adding a valid symbol and checking that it exists (fixed by this change)
- Getting an existing error symbol
- Getting a non existing error symbol
- Adding an invalid error symbol (fixed by this change)
- Adding an error symbol with a code that already
  exists (fixed by this change)

Therefore, issue #268 was fixed
2023-09-01 16:11:49 +02:00
Michael Davidsaver
485ac85fa5 appveyor remove skip_commits: 2023-09-01 16:11:49 +02:00
1148931226 On Vxworks clone std* file handles because they are thread specific 2023-09-01 14:15:56 +02:00
73f80cced9 Merge branch '7.0' into PSI-7.0 2023-08-21 14:14:16 +02:00
152c72bab0 fix submodule pvData to use our fork 2023-08-14 15:42:10 +02:00
Ralph Lange
00dc55b8a2 Fix usage info in .tools/make-tar.sh 2023-08-08 10:29:09 +02:00
Rolf Keitel
524f81b8bd Doc updates to PINI, PHAS & EVNT 2023-07-25 11:36:44 -05:00
Michael Davidsaver
80e62031e9 doc 2023-07-23 08:43:32 -07:00
Minijackson
d87fd0db01 Fix MAKEFLAGS parsing with Make 4.4+
Since Make version 4.4, MAKEFLAGS also contains long options and
overridden variables on the command-line[1].

[1]: https://git.savannah.gnu.org/cgit/make.git/tree/NEWS?h=4.4#n67

This means that parsing by filtering out '--%' doesn't work reliably
anymore, since it doesn't remove overrides:

Running 'make VAR=quacks' gives 'MAKEFLAGS=" -- VAR=quacks"', and
'checkflags' would understand that flags -q, -s, ... were set.

This would get transmitted below into 'QUIET_FLAGS' and
'QUESTION_FLAG', then passed to the 'genVersionHeader.pl' as '-i' and
'-q'.

The result would be that 'genVersionHeader.pl' would never create the
version header (only check for its up-to-date status), leading to
confusing errors:

  ../misc/epicsRelease.c:25:32: error: expected ')' before 'EPICS_VCS_VERSION'
     25 |     printf ( "## %s\n", "Rev. " EPICS_VCS_VERSION );
        |            ~                   ^~~~~~~~~~~~~~~~~~

The NEWS file[1] recommends using 'firstword', but unfortunately this is
not compatible with GNUMake < 3.82.
2023-07-22 08:36:43 -07:00
Ralph Lange
6636b4b9e7 libCom: check calloc() failure in RTEMS-posix/osdMessageQueue.c 2023-07-22 08:35:59 -07:00
Ralph Lange
b51d1de283 libCom: fix possible memory leak in RTEMS-posix/osdMessageQueue.c
found by static code analysis (cppcheck @ sonarqube)
2023-07-22 08:35:59 -07:00
Ralph Lange
38c99df2e0 libCom: fix possible memory leaks in iocLogServer.c
found by static code analysis (cppcheck @ sonarqube)
2023-07-22 08:35:59 -07:00
Ralph Lange
688195a273 libCom: handle realloc() failures correctly in macEnv.c
found by static code analysis (cppcheck @ sonarqube)
2023-07-22 08:35:59 -07:00
Ralph Lange
d691acc001 ca: init local variable in catime.c
found by static code analysis (cppcheck @ sonarqube)
2023-07-22 08:35:59 -07:00
Ralph Lange
059d32a975 db: init struct members in dbChannel.c
found by static code analysis (cppcheck @ sonarqube)
2023-07-22 08:35:59 -07:00
Henrique Silva
5c99031157 Add missing AFTC documentation to records
Fixes #313
2023-07-22 08:33:19 -07:00
Henrique Silva
a01c671399 Add missing HYST field documentation to longin record 2023-07-22 08:33:19 -07:00
Minijackson
b2c80efd33 release notes: link release notes from submodules'
fixes #226
2023-07-22 08:33:08 -07:00
Karl Vestin
39b6fa26da Added unit test for binary output record type 2023-07-22 08:32:16 -07:00
Karl Vestin
3ee6097ab7 Fixes #361 2023-07-22 08:30:44 -07:00
Emilio Perez
500a57738b Validate target record name when creating an alias
This fixes issue #312 by printing an error when a field is specified
2023-07-22 08:29:09 -07:00
AlexWells
f488765631 Add tests for PR#310
Also add missing NULL/empty checks
2023-07-22 08:28:25 -07:00
Sebastian Marsching
3a2d225682 Detect error in fprintf and return (fixes #309).
fprintf returns a negative value in order to signal an error. We have to
detect this situation in epicsStrPrintEscaped and return a negative
when fprintf returns a negative value in order to give the calling code
a chance to detect this situation.

The old implementation (of simply accumulating the return values of
fprintf) was wrong anyway, because it would not only lead to an error in
fprintf to be lost but would also cause the returned number to be too
small (not representing the actual number of bytes written) in such a
case.

The only case where the old implementation would work correctly was when
all calls to fprintf succeeded or all these calls failed.
2023-07-22 08:27:32 -07:00
Doug Murray
1d056c6fe4 Add support for CA tools timeout from environment variable EPICS_CLI_TIMEOUT 2023-07-22 08:24:22 -07:00
0f086126b7 fix escaped quotes in quoted strings
Some checks failed
Check EditorConfig / editorconfig (push) Failing after 1s
Base / Cross linux-aarch64 (push) Failing after 2s
Base / Cross linux-arm gnueabi (push) Failing after 1s
Base / Cross linux-arm gnueabihf (push) Failing after 2s
Base / Fedora-33 (push) Failing after 2s
Base / CentOS-7 (push) Failing after 5s
Base / Fedora-latest (push) Failing after 1s
Base / Win2019 mingw (push) Has been cancelled
Base / Ub-20 clang-10 C++11 (push) Has been cancelled
Base / MacOS clang-12 (push) Has been cancelled
Base / Ub-20 clang-10 (push) Has been cancelled
Base / Ub-20 gcc-9 + RT-5.1 beatnik (push) Has been cancelled
Base / Ub-20 gcc-9 + RT-4.10 (push) Has been cancelled
Base / Ub-20 gcc-9 + RT-4.9 (push) Has been cancelled
Base / Ub-20 gcc-9 + RT-5.1 pc686 (push) Has been cancelled
Base / Ub-20 gcc-9 + RT-5.1 xilinx_zynq_a9_qemu (push) Has been cancelled
Base / Ub-20 gcc-9 + RT-5.1 uC5282 (push) Has been cancelled
Base / Ub-20 gcc-9 + MinGW (push) Has been cancelled
Base / Ub-20 gcc-9 unsigned char (push) Has been cancelled
Base / Ub-20 gcc-9 C++11, static (push) Has been cancelled
Base / Ub-20 gcc-9 + MinGW, static (push) Has been cancelled
Base / Win2019 MSC-19 (push) Has been cancelled
Base / Win2019 MSC-19, debug (push) Has been cancelled
Base / Win2019 MSC-19, static (push) Has been cancelled
2023-06-22 16:37:29 +02:00
5b058d7d27 Merge branch '7.0' into PSI-7.0 2023-06-22 16:36:32 +02:00
Michael Davidsaver
42604fc794 Allow clang with GCC compilerSpecific.h
Some checks failed
Check EditorConfig / editorconfig (push) Failing after 1s
Base / Cross linux-aarch64 (push) Failing after 2s
Base / Cross linux-arm gnueabi (push) Failing after 1s
Base / Cross linux-arm gnueabihf (push) Failing after 2s
Base / CentOS-7 (push) Failing after 1s
Base / Fedora-33 (push) Failing after 2s
Base / Fedora-latest (push) Failing after 1s
Base / Ub-20 clang-10 C++11 (push) Has been cancelled
Base / Ub-20 gcc-9 + RT-4.10 (push) Has been cancelled
Base / Ub-20 gcc-9 + RT-5.1 xilinx_zynq_a9_qemu (push) Has been cancelled
Base / Win2019 mingw (push) Has been cancelled
Base / Ub-20 gcc-9 + MinGW (push) Has been cancelled
Base / Ub-20 gcc-9 unsigned char (push) Has been cancelled
Base / Ub-20 gcc-9 C++11, static (push) Has been cancelled
Base / Ub-20 gcc-9 + MinGW, static (push) Has been cancelled
Base / MacOS clang-12 (push) Has been cancelled
Base / Ub-20 clang-10 (push) Has been cancelled
Base / Ub-20 gcc-9 + RT-5.1 beatnik (push) Has been cancelled
Base / Ub-20 gcc-9 + RT-4.9 (push) Has been cancelled
Base / Ub-20 gcc-9 + RT-5.1 pc686 (push) Has been cancelled
Base / Ub-20 gcc-9 + RT-5.1 uC5282 (push) Has been cancelled
Base / Win2019 MSC-19 (push) Has been cancelled
Base / Win2019 MSC-19, debug (push) Has been cancelled
Base / Win2019 MSC-19, static (push) Has been cancelled
Makes it easier to run clang derivative analysis tools
on builds configured for GCC.
2023-06-13 08:47:13 -07:00
Michael Davidsaver
4ecc0daa79 make mapDBFToDBR[] const 2023-06-13 08:47:13 -07:00
Michael Davidsaver
5a1f3ecc8b doc: note when some record types were introduced 2023-05-18 11:57:41 -07:00
Michael Davidsaver
cb97d662a7 doc 2023-05-01 10:31:01 -07:00
Michael Davidsaver
d4fab0d20e iocsh: dbCompleteRecord() missing NULL check 2023-05-01 10:31:01 -07:00
Michael Davidsaver
0c13e6ba6c iocsh: tab completion handle iocshArgArgv 2023-05-01 10:31:01 -07:00
Michael Davidsaver
8f1243da40 epicsSingleton: eliminate global ctor with >= c++11 2023-05-01 10:25:29 -07:00
Michael Davidsaver
fe9995c0b5 Update recommendation for CONFIGURE_MAXIMUM_FILE_DESCRIPTORS 2023-05-01 09:06:59 -07:00
Freddie Akeroyd
f56412d6a5 WIN32: use FlsAlloc() to cleanup epicsThreadOSD
Adjust macros for compiling for older MSVC/Win SDK versions

Try to cover missing fibres include in 7.0 SDK

Support Windows XP and above.
Also removed explicit define of _WIN32_WINNT in code if it has not
been passed on compile line. This is possibly a matter for
further discussion
2023-05-01 09:03:52 -07:00
Érico Nogueira
07d18c55ba Clean whitespace in makeBaseApp template README 2023-05-01 09:03:52 -07:00
Minijackson
9f97f25669 ci: add cross-compilation tests for aarch64, arm soft and hard float 2023-05-01 09:03:52 -07:00
912a82c0b5 replace deprecated decrementing volatile with atomic decrement 2023-05-01 09:03:52 -07:00
acf2241fd0 Some archs define ECHO in termios.h 2023-05-01 09:03:52 -07:00
Eva Lott
b878295d06 Added the new annotation EPICS_PRINTF_FMT 2023-05-01 09:03:45 -07:00
f41f11c7f6 fix compiler warning on 32 bit systems 2023-04-28 09:35:49 -05:00
Michael Davidsaver
216359974c update release notes 2023-04-18 13:11:59 -05:00
Torsten Bögershausen
17ad04505e Change compiler for FreeBSD 13: Use clang
FreeBSD 13 uses clang, not gcc, any more.
GNU_DIR must be set to /usr/local

Note: This change touches both the x86 and the x86_64 files.
It was tested on 'amd64' system only, which is x86_64
2023-04-18 09:51:33 -05:00
Michael Davidsaver
3500a02034 iocsh: expose dbCreateAlias 2023-04-18 09:51:28 -05:00
Michael Davidsaver
52b18d56a0 dbCreateAlias fixup error handling 2023-04-18 09:51:28 -05:00
Michael Davidsaver
5507646ce7 posix: optimize epicsThreadOnce()
Use atomic ops to short circuit when already initialized
2023-04-18 09:51:24 -05:00
Michael Davidsaver
625c2ef159 epicsThreadPerform: time epicsThreadOnce() 2023-04-18 09:51:24 -05:00
Jure Varlec
bdaca51d96 Update shareLib API docs, directing the reader to makeAPIheader.pl 2023-04-18 09:51:21 -05:00
Jure Varlec
9655b78e11 Update release notes: ts filter 2023-04-18 09:51:07 -05:00
Jure Varlec
e11f88017d ts filter: port to the new db_field_log 2023-04-18 09:51:07 -05:00
Jure Varlec
8a3020033e ts filter: replace cantProceed with a non-fatal error msg 2023-04-18 09:51:07 -05:00
Jure Varlec
bd1af9ac95 ts filter: fix unused variable warnings 2023-04-18 09:51:07 -05:00
Jure Varlec
d5959ca20a ts filter: handle calloc failures 2023-04-18 09:51:07 -05:00
Jure Varlec
e10dcede7d ts filter: clear the dtor field after destruction 2023-04-18 09:51:07 -05:00
Jure Varlec
c042b08ab0 Extend timestamp filter, giving access to the record timestamp 2023-04-18 09:51:07 -05:00
91 changed files with 2902 additions and 352 deletions

View File

@@ -25,22 +25,6 @@ init:
# Set clone depth (do not fetch complete history)
clone_depth: 5
# Skip commits affecting only specific files
skip_commits:
files:
# Removed - 'documentation/*'
# Unfortunately Appveyor only looks at the HEAD (latest) commit, so if
# the last commit pushed to a PR only updates the Release Notes the whole
# PR build gets skipped. GitHub Actions looks at the complete list
# of files changed since its last build, which is much more useful.
- 'startup/*'
- '.github/*'
- '.tools/*'
- '.gitattributes'
- '.lgtm.yml'
- '**/*.html'
- '**/*.md'
#---------------------------------#
# build matrix configuration #
#---------------------------------#

View File

@@ -32,22 +32,6 @@ init:
# Set clone depth (do not fetch complete history)
clone_depth: 5
# Skip commits affecting only specific files
skip_commits:
files:
# Removed - 'documentation/*'
# Unfortunately Appveyor only looks at the HEAD (latest) commit, so if
# the last commit pushed to a PR only updates the Release Notes the whole
# PR build gets skipped. GitHub Actions looks at the complete list
# of files changed since its last build, which is much more useful.
- 'startup/*'
- '.github/*'
- '.tools/*'
- '.lgtm.yml'
- '.gitattributes'
- '**/*.html'
- '**/*.md'
#---------------------------------#
# build matrix configuration #
#---------------------------------#

View File

@@ -133,13 +133,15 @@ jobs:
- os: windows-2019
cmp: vs2019
configuration: default
configuration: debug
name: "Win2019 MSC-19"
extra: "CMD_CXXFLAGS=-analysis"
- os: windows-2019
cmp: vs2019
configuration: static
configuration: static-debug
name: "Win2019 MSC-19, static"
extra: "CMD_CXXFLAGS=-analysis"
- os: windows-2019
cmp: vs2019
@@ -151,6 +153,26 @@ jobs:
configuration: default
name: "Win2019 mingw"
# Cross builds
- os: ubuntu-latest
cmp: gcc
configuration: default
name: "Cross linux-aarch64"
cross: linux-aarch64
- os: ubuntu-latest
cmp: gcc
configuration: default
name: "Cross linux-arm gnueabi"
cross: linux-arm@arm-linux-gnueabi
- os: ubuntu-latest
cmp: gcc
configuration: default
name: "Cross linux-arm gnueabihf"
cross: linux-arm@arm-linux-gnueabihf
steps:
- uses: actions/checkout@v3
with:

3
.gitmodules vendored
View File

@@ -1,6 +1,7 @@
[submodule "modules/pvData"]
path = modules/pvData
url = https://github.com/epics-base/pvDataCPP
# url = https://github.com/epics-base/pvDataCPP
url = git@git.psi.ch:epics_base/pvData.git
branch = master
[submodule "modules/pvAccess"]
path = modules/pvAccess

View File

@@ -20,9 +20,9 @@ maybedie() {
usage() {
cat <<EOF >&2
usage: $0 [-v] [-s] <rev> [<outfile> [<prefix>]]
usage: $0 [-v] [-d] <rev> [<outfile> [<prefix>]]
<rev> may be any git revision spec. (tag, branch, or commit id).
<rev> may be any git revision spec. (tag or, using -d, branch or commit id).
If provided, <outfile> must end with ".tar", ".tar.gz" or ".tar.bz2".
If <outfile> is omitted, "base-<rev>.tar.gz" will be used.

View File

@@ -83,8 +83,9 @@ IOCS_APPL_TOP = $(INSTALL_ABSOLUTE)
#-------------------------------------------------------
# How to portably check the flags to make
makeflags := $(firstword $(filter-out -,$(filter-out --%,$(MAKEFLAGS))))
define checkflags
make-$1 = $(findstring $1,$(filter-out --%,$(MAKEFLAGS)))
make-$1 := $(findstring $1,$(makeflags))
endef
# This is extensible to most single letter flags:
$(foreach flag,s q, $(eval $(call checkflags,$(flag))))

View File

@@ -7,6 +7,10 @@
# Include definitions common to all Unix targets
include $(CONFIG)/os/CONFIG.Common.UnixCommon
GNU = NO
CMPLR_CLASS = clang
CC = clang
CCC = clang++
OS_CLASS = freebsd

View File

@@ -5,3 +5,4 @@
#Include definitions common to unix hosts
include $(CONFIG)/os/CONFIG.UnixCommon.Common
CMPLR_CLASS = clang

View File

@@ -2,6 +2,7 @@
# Definitions for freebsd-x86 host - freebsd-x86 target builds
# Sites may override these definitions in CONFIG_SITE.freebsd-x86.freebsd-x86
#-------------------------------------------------------
GNU_DIR=/usr/local
# Include common gnu compiler definitions
include $(CONFIG)/CONFIG.gnuCommon

View File

@@ -2,6 +2,7 @@
# Definitions for freebsd-x86_64 host - freebsd-x86_64 target builds
# Sites may override these definitions in CONFIG_SITE.freebsd-x86_64.freebsd-x86_64
#-------------------------------------------------------
GNU_DIR=/usr/local
# Include common gnu compiler definitions
include $(CONFIG)/CONFIG.gnuCommon

View File

@@ -9,12 +9,49 @@ important to read more than just the first section to understand everything that
has changed in each release.
The PVA submodules each have their own individual sets of release notes which
should also be read to understand what has changed since earlier releases.
should also be read to understand what has changed since earlier releases:
- [normativeTypes](https://github.com/epics-base/normativeTypesCPP/blob/master/documentation/RELEASE_NOTES.md)
- [pvAccess](http://epics-base.github.io/pvAccessCPP/pvarelease_notes.html)
- [pvData](http://epics-base.github.io/pvDataCPP/release_notes.html)
- [pvDatabase](https://github.com/epics-base/pvDatabaseCPP/blob/master/documentation/RELEASE_NOTES.md)
- [pva2pva](https://epics-base.github.io/pva2pva/release_notes.html)
- [pvaClient](https://github.com/epics-base/pvaClientCPP/blob/master/documentation/RELEASE_NOTES.md)
**This version of EPICS has not been released yet.**
## Changes made on the 7.0 branch since 7.0.7
### Add `$EPICS_CLI_TIMEOUT`
Add support for CA tools timeout from environment variable `$EPICS_CLI_TIMEOUT`
which sets the default the default timeout for `caget` et al.
The `-w` argument continues to take precedence.
### Fixed leak from a non-EPICS thread on WIN32
On Windows targets, if a thread not created by `epicsThreadCreate*()` directly
or indirectly calls an `epicsThread*()` function, a specific tracking struct
is allocated. Prior to this release the allocation would not be `free()`d,
resulting in a memory leak.
A similar issue on POSIX targets was previously fixed.
### Change compiler for FreeBSD to clang
The default compiler for FreeBSD targets changes from GCC to clang.
### Expose `dbCreateAlias` in IOC shell
Add a new IOC shell command `dbCreateAlias` allow record aliases to be added.
Intended for use before `iocInit`. eg. to add an alias "bar" for a record "foo".
```
dbLoadRecords("some.db") # includes: record(ai, "foo") { ...
dbCreateAlias("foo", "bar")
iocInit()
```
### dbEvent eventsRemaining missed on cancel
In some cases, RSRV may queue a subscription update, but not flush it.
@@ -48,6 +85,27 @@ The compress record now supports the use of partially-filled buffers when using
any of the N-to-one algorithms. This is achieved by setting the new field `PBUF`
to `YES`.
### Extended timestamp channel filter
The `"ts"` filter can now retrieve the record's timestamp in several numeric
and string formats, some of which support full nanosecond precision.
Hal$ caget -a test:channel
test:channel 2021-03-11 18:23:48.265386 42
Hal$ caget -f9 'test:channel.{"ts": {"num": "dbl"}}'
test:channel.{"ts": {"num": "dbl"}} 984331428.265386105
Hal$ caget 'test:channel.{"ts": {"str": "iso"}}'
test:channel.{"ts": {"str": "iso"}} 2021-03-11T18:23:48.265386+0100
Hal$ caget -f1 'test:channel.{"ts": {"num": "ts"}}'
test:channel.{"ts": {"num": "ts"}} 2 984331428.0 265386163.0
More information is included in the filters documentation, which can be found in
the `html/filters.html` document that is generated during the build
### Allow adding new error symbols at any time
`errSymbolAdd` can now be called after early initialization.
### Add conditional output (OOPT) to the longout record
The longout record can now be configured using its new OOPT and OOCH fields
@@ -227,6 +285,32 @@ This release fixed the leak on POSIX targets.
See the associated github [issue 241](https://github.com/epics-base/epics-base/issues/241)
for WIN32 status.
### Fixed leak from a non-EPICS thread
On some targets, if a thread not created by `epicsThreadCreate*()` directly
or indirectly calls an `epicsThread*()` function, a specific tracking struct
is allocated.
Prior to this release, on POSIX and WIN32 targets, this
allocation would not be `free()`d, resulting in a memory leak.
This release fixed the leak on POSIX and WIN32 targets (excluding
MSVC before vs2012, and the WINE runtime).
### Fixed leak from a non-EPICS thread
On some targets, if a thread not created by `epicsThreadCreate*()` directly
or indirectly calls an `epicsThread*()` function, a specific tracking struct
is allocated.
Prior to this release, on POSIX and WIN32 targets, this
struct would not be `free()`d, resulting in a memory leak.
This release fixed the leak on POSIX targets.
See the associated github [issue 241](https://github.com/epics-base/epics-base/issues/241)
for WIN32 status.
### Fix `CHECK_RELEASE = WARN`
This now works again, it was broken in 2019 (7.0.3.1) by an errant commit.

View File

@@ -450,7 +450,7 @@ void timeIt ( tf *pfunc, ti *pItems, unsigned iterations,
epicsTimeStamp end_time;
epicsTimeStamp start_time;
double delay;
unsigned inlineIter;
unsigned inlineIter = 0;
epicsTimeGetCurrent ( &start_time );
(*pfunc) ( pItems, iterations, &inlineIter );

View File

@@ -392,6 +392,8 @@ int main (int argc, char *argv[])
LINE_BUFFER(stdout); /* Configure stdout buffering */
use_ca_timeout_env ( &caTimeout);
while ((opt = getopt(argc, argv, ":taicnhsSVe:f:g:l:#:d:0:w:p:F:")) != -1) {
switch (opt) {
case 'h': /* Print usage */
@@ -437,8 +439,8 @@ int main (int argc, char *argv[])
if(epicsScanDouble(optarg, &caTimeout) != 1)
{
fprintf(stderr, "'%s' is not a valid timeout value "
"- ignored. ('caget -h' for help.)\n", optarg);
caTimeout = DEFAULT_TIMEOUT;
"- ignored, using '%.1f'. ('caget -h' for help.)\n",
optarg, caTimeout);
}
break;
case '#': /* Array count */

View File

@@ -141,6 +141,8 @@ int main (int argc, char *argv[])
LINE_BUFFER(stdout); /* Configure stdout buffering */
use_ca_timeout_env ( &caTimeout);
while ((opt = getopt(argc, argv, ":nhVw:s:p:")) != -1) {
switch (opt) {
case 'h': /* Print usage */
@@ -150,11 +152,16 @@ int main (int argc, char *argv[])
printf( "\nEPICS Version %s, CA Protocol version %s\n", EPICS_VERSION_STRING, ca_version() );
return 0;
case 'w': /* Set CA timeout value */
/*
* epicsScanDouble is a macro defined as epicsParseDouble,
* (found in modules/libcom/src/misc) which will only
* change caTimeout here if it finds an acceptable value.
*/
if(epicsScanDouble(optarg, &caTimeout) != 1)
{
fprintf(stderr, "'%s' is not a valid timeout value "
"- ignored. ('cainfo -h' for help.)\n", optarg);
caTimeout = DEFAULT_TIMEOUT;
"- ignored, using '%.1f'. ('cainfo -h' for help.)\n",
optarg, caTimeout);
}
break;
case 's': /* ca_client_status interest level */

View File

@@ -219,6 +219,8 @@ int main (int argc, char *argv[])
LINE_BUFFER(stdout); /* Configure stdout buffering */
use_ca_timeout_env ( &caTimeout);
while ((opt = getopt(argc, argv, ":nhVm:sSe:f:g:l:#:0:w:t:p:F:")) != -1) {
switch (opt) {
case 'h': /* Print usage */
@@ -251,11 +253,16 @@ int main (int argc, char *argv[])
}
break;
case 'w': /* Set CA timeout value */
/*
* epicsScanDouble is a macro defined as epicsParseDouble,
* (found in modules/libcom/src/misc) which will only
* change caTimeout here if it finds an acceptable value.
*/
if(epicsScanDouble(optarg, &caTimeout) != 1)
{
fprintf(stderr, "'%s' is not a valid timeout value "
"- ignored. ('camonitor -h' for help.)\n", optarg);
caTimeout = DEFAULT_TIMEOUT;
"- ignored, using '%.1f'. ('camonitor -h' for help.)\n",
optarg, caTimeout);
}
break;
case '#': /* Array count */

View File

@@ -284,6 +284,8 @@ int main (int argc, char *argv[])
LINE_BUFFER(stdout); /* Configure stdout buffering */
putenv("POSIXLY_CORRECT="); /* Behave correct on GNU getopt systems */
use_ca_timeout_env ( &caTimeout);
while ((opt = getopt(argc, argv, ":cnlhatsVS#:w:p:F:")) != -1) {
switch (opt) {
case 'h': /* Print usage */
@@ -318,11 +320,16 @@ int main (int argc, char *argv[])
request = callback;
break;
case 'w': /* Set CA timeout value */
/*
* epicsScanDouble is a macro defined as epicsParseDouble,
* (found in modules/libcom/src/misc) which will only
* change caTimeout here if it finds an acceptable value.
*/
if(epicsScanDouble(optarg, &caTimeout) != 1)
{
fprintf(stderr, "'%s' is not a valid timeout value "
"- ignored. ('caput -h' for help.)\n", optarg);
caTimeout = DEFAULT_TIMEOUT;
"- ignored, using '%.1f'. ('caput -h' for help.)\n",
optarg, caTimeout);
}
break;
case '#': /* Array count */
@@ -337,7 +344,7 @@ int main (int argc, char *argv[])
if (sscanf(optarg,"%u", &caPriority) != 1)
{
fprintf(stderr, "'%s' is not a valid CA priority "
"- ignored. ('caget -h' for help.)\n", optarg);
"- ignored. ('caput -h' for help.)\n", optarg);
caPriority = DEFAULT_CA_PRIORITY;
}
if (caPriority > CA_PRIORITY_MAX) caPriority = CA_PRIORITY_MAX;

View File

@@ -28,6 +28,7 @@
#include <alarm.h>
#include <epicsTime.h>
#include <epicsStdlib.h>
#include <epicsString.h>
#include <cadef.h>
@@ -53,7 +54,7 @@ char fieldSeparator = ' '; /* OFS default is whitespace */
int enumAsNr = 0; /* used for -n option - get DBF_ENUM as number */
int charArrAsStr = 0; /* used for -S option - treat char array as (long) string */
double caTimeout = 1.0; /* wait time default (see -w option) */
double caTimeout = DEFAULT_TIMEOUT; /* wait time default (see -w option) */
capri caPriority = DEFAULT_CA_PRIORITY; /* CA Priority */
#define TIMETEXTLEN 28 /* Length of timestamp text buffer */
@@ -639,3 +640,22 @@ int connect_pvs (pv* pvs, int nPvs)
}
return returncode;
}
/* Set the timeout to EPICS_CLI_TIMEOUT */
void use_ca_timeout_env ( double* timeout)
{
const char* tmoStr; /* contents of environment var */
if ((tmoStr = getenv("EPICS_CLI_TIMEOUT")) != NULL && timeout != NULL)
{
if(epicsScanDouble(tmoStr, timeout) != 1)
{
fprintf(stderr, "'%s' is not a valid timeout value "
"(from 'EPICS_CLI_TIMEOUT' in the environment) - "
"ignored. (use '-h' for help.)\n", tmoStr);
*timeout = DEFAULT_TIMEOUT;
}
}
}

View File

@@ -100,6 +100,7 @@ extern char *dbr2str (const void *value, unsigned type);
extern void print_time_val_sts (pv *pv, unsigned long reqElems);
extern int create_pvs (pv *pvs, int nPvs, caCh *pCB );
extern int connect_pvs (pv *pvs, int nPvs );
extern void use_ca_timeout_env (double* timeout);
/*
* no additions below this endif

View File

@@ -73,7 +73,7 @@ epicsExportAddress(int, dbAccessDebugPUTF);
DB_LOAD_RECORDS_HOOK_ROUTINE dbLoadRecordsHook = NULL;
static short mapDBFToDBR[DBF_NTYPES] = {
static const short mapDBFToDBR[DBF_NTYPES] = {
/* DBF_STRING => */ DBR_STRING,
/* DBF_CHAR => */ DBR_CHAR,
/* DBF_UCHAR => */ DBR_UCHAR,

View File

@@ -575,15 +575,10 @@ long dbChannelOpen(dbChannel *chan)
}
/* Set up type probe */
probe.type = dbfl_type_val;
probe.ctx = dbfl_context_read;
memset(&probe, 0, sizeof(probe));
probe.field_type = dbChannelExportType(chan);
probe.no_elements = dbChannelElements(chan);
probe.field_size = dbChannelFieldSize(chan);
probe.sevr = NO_ALARM;
probe.stat = NO_ALARM;
probe.time.secPastEpoch = 0;
probe.time.nsec = 0;
p = probe;

View File

@@ -91,20 +91,25 @@ For example these rates are all valid:
1 second
2 Hertz
The B<PINI> field specifies record processing at initialization. If it is set
to YES during database configuration, the record is processed once at IOC
initialization (before the normal scan tasks are started).
The B<PINI> field specifies record processing at initialization. It can have the
values NO, YES, RUN, RUNNING, PAUSE, and PAUSED. If it is set to YES during
database configuration, the record is processed once at IOC initialization
(before the normal scan tasks are started). For the other values see
L<menuPini.dbd|menuPini> for more details.
The B<PHAS> field orders the records within a specific SCAN group. This is not
meaningful for passive records. All records of a specified phase are processed
before those with higher phase number. It is generally better practice to use
linked passive records to enforce the order of processing rather than a phase
number.
The B<PHAS> field orders the records processed within a specific SCAN group or
PINI processing phase. All records of a specified phase are processed before
those with higher phase number. It is generally better practice to use linked
passive records to enforce the order of processing rather than a phase number.
If the PINI field is set to NO, the PHAS field is not meaningful for passive
records.
The B<EVNT> field specifies an event number. This event number is used if the
SCAN field is set to C<Event>. All records with scan type C<Event> and the
same EVNT value will be processed when a call to post_event for EVNT is made.
The call to post_event is: post_event(short event_number).
The B<EVNT> field specifies an event name or number. This event identifier is
used if the SCAN field is set to C<Event>. All records with scan type C<Event>
and the same EVNT value will be processed when that event is signalled by other
software running in the IOC, either by calling C<post_event(int event)> for
numeric events, or by calling C<postEvent()> with an event handle previously
looked up using C<eventNameToHandle()> for named events.
The B<PRIO> field specifies the scheduling priority for processing records
with SCAN=C<I/O Event> and asynchronous record completion tasks.

View File

@@ -410,8 +410,21 @@ DBCORE_API long dbLoadLinkArray(struct link *, short dbrType, void *pbuffer,
DBCORE_API long dbGetNelements(const struct link *plink, long *pnElements);
DBCORE_API int dbIsLinkConnected(const struct link *plink); /* 0 or 1 */
DBCORE_API int dbGetLinkDBFtype(const struct link *plink);
/** \brief Fetch current value from link.
* \param dbrType Database DBR code
* \param pbuffer Destination buffer
* \param nRequest If !NULL. Caller initializes with number of elements requested,
* On success, set to number of elements written to pbuffer.
* \return 0 on success
*
* When called with `nRequest==NULL`, treated as a request for one (1)
* element.
*
* see lset::getValue
*/
DBCORE_API long dbTryGetLink(struct link *, short dbrType, void *pbuffer,
long *nRequest);
/** see dbTryGetLink() */
DBCORE_API long dbGetLink(struct link *, short dbrType, void *pbuffer,
long *options, long *nRequest);
DBCORE_API long dbGetControlLimits(const struct link *plink, double *low,

View File

@@ -39,12 +39,17 @@ extern "C" {
* will adjust automatically, it just compares field sizes.
*/
union native_value {
epicsInt8 dbf_char;
epicsInt16 dbf_short;
epicsEnum16 dbf_enum;
epicsInt32 dbf_long;
epicsFloat32 dbf_float;
epicsFloat64 dbf_double;
epicsInt8 dbf_char;
epicsUInt8 dbf_uchar;
epicsInt16 dbf_short;
epicsUInt16 dbf_ushort;
epicsEnum16 dbf_enum;
epicsInt32 dbf_long;
epicsUInt32 dbf_ulong;
epicsInt64 dbf_int64;
epicsUInt64 dbf_uint64;
epicsFloat32 dbf_float;
epicsFloat64 dbf_double;
#ifdef DB_EVENT_LOG_STRINGS
char dbf_string[MAX_STRING_SIZE];
#endif

View File

@@ -75,7 +75,7 @@ DBCORE_API void recGblInheritSevr(int msMode, void *precord, epicsEnum16 stat,
epicsEnum16 sevr);
DBCORE_API int recGblSetSevrMsg(void *precord, epicsEnum16 new_stat,
epicsEnum16 new_sevr,
const char *msg, ...) EPICS_PRINTF_STYLE(4,5);
EPICS_PRINTF_FMT(const char *msg), ...) EPICS_PRINTF_STYLE(4,5);
DBCORE_API int recGblSetSevrVMsg(void *precord, epicsEnum16 new_stat,
epicsEnum16 new_sevr,
const char *msg, va_list args);

View File

@@ -139,14 +139,16 @@ char** dbCompleteRecord(const char *cword)
if(!prefix.empty() || !suggestions.empty()) {
ret = (char**)malloc(sizeof(*ret)*(2u + suggestions.size()));
ret[0] = prefix.dup();
size_t n=1u;
for(suggestions_t::iterator it(suggestions.begin()), end(suggestions.end());
it!=end; ++it)
{
ret[n++] = it->dup();
if(ret) {
ret[0] = prefix.dup();
size_t n=1u;
for(suggestions_t::iterator it(suggestions.begin()), end(suggestions.end());
it!=end; ++it)
{
ret[n++] = it->dup();
}
ret[n] = NULL;
}
ret[n] = NULL;
}
return ret;

View File

@@ -1258,7 +1258,7 @@ static void dbAlias(char *name, char *alias)
DBENTRY dbEntry;
DBENTRY *pdbEntry = &dbEntry;
if(dbRecordNameValidate(alias))
if(dbRecordNameValidate(alias) || dbRecordNameValidate(name))
return;
dbInitEntry(savedPdbbase, pdbEntry);

View File

@@ -9,6 +9,7 @@
\*************************************************************************/
#include "iocsh.h"
#include "errSymTbl.h"
#include "dbStaticIocRegister.h"
#include "dbStaticLib.h"
@@ -217,6 +218,40 @@ static void dbReportDeviceConfigCallFunc(const iocshArgBuf *args)
dbReportDeviceConfig(*iocshPpdbbase,stdout);
}
static const iocshArg dbCreateAliasArg0 = { "record",iocshArgStringRecord};
static const iocshArg dbCreateAliasArg1 = { "alias",iocshArgStringRecord};
static const iocshArg * const dbCreateAliasArgs[] = {&argPdbbase,&dbCreateAliasArg0, &dbCreateAliasArg1};
static const iocshFuncDef dbCreateAliasFuncDef = {
"dbCreateAlias",
3,
dbCreateAliasArgs,
"Add a new record alias.\n"
"\n"
"Example: dbCreateAlias pdbbase record:name new:alias\n",
};
static void dbCreateAliasCallFunc(const iocshArgBuf *args)
{
DBENTRY ent;
long status;
dbInitEntry(*iocshPpdbbase, &ent);
if(!args[1].sval || !args[2].sval) {
status = S_dbLib_recNotFound;
} else {
status = dbFindRecord(&ent, args[1].sval);
if(!status) {
status = dbCreateAlias(&ent, args[2].sval);
}
}
dbFinishEntry(&ent);
if(status) {
fprintf(stderr, "Error: %ld %s\n", status, errSymMsg(status));
iocshSetError(1);
}
}
void dbStaticIocRegister(void)
{
iocshRegister(&dbDumpPathFuncDef, dbDumpPathCallFunc);
@@ -234,4 +269,5 @@ void dbStaticIocRegister(void)
iocshRegister(&dbPvdDumpFuncDef, dbPvdDumpCallFunc);
iocshRegister(&dbPvdTableSizeFuncDef,dbPvdTableSizeCallFunc);
iocshRegister(&dbReportDeviceConfigFuncDef, dbReportDeviceConfigCallFunc);
iocshRegister(&dbCreateAliasFuncDef, dbCreateAliasCallFunc);
}

View File

@@ -1652,6 +1652,7 @@ long dbCreateAlias(DBENTRY *pdbentry, const char *alias)
dbRecordNode *pnewnode;
DBENTRY tempEntry;
PVDENTRY *ppvd;
long status;
if (!precordType)
return S_dbLib_recordTypeNotFound;
@@ -1664,9 +1665,10 @@ long dbCreateAlias(DBENTRY *pdbentry, const char *alias)
return S_dbLib_recNotFound;
dbInitEntry(pdbentry->pdbbase, &tempEntry);
if (!dbFindRecord(&tempEntry, alias))
return S_dbLib_recExists;
status = dbFindRecord(&tempEntry, alias);
dbFinishEntry(&tempEntry);
if (!status)
return S_dbLib_recExists;
pnewnode = dbCalloc(1, sizeof(dbRecordNode));
pnewnode->recordname = epicsStrDup(alias);
@@ -1676,15 +1678,16 @@ long dbCreateAlias(DBENTRY *pdbentry, const char *alias)
precnode->flags |= DBRN_FLAGS_HASALIAS;
ellInit(&pnewnode->infoList);
ellAdd(&precordType->recList, &pnewnode->node);
precordType->no_aliases++;
ppvd = dbPvdAdd(pdbentry->pdbbase, precordType, pnewnode);
if (!ppvd) {
errMessage(-1, "dbCreateAlias: Add to PVD failed");
free(pnewnode);
return -1;
}
ellAdd(&precordType->recList, &pnewnode->node);
precordType->no_aliases++;
return 0;
}

View File

@@ -39,7 +39,9 @@ char *dbRecordName(DBENTRY *pdbentry);
char *dbGetStringNum(DBENTRY *pdbentry);
long dbPutStringNum(DBENTRY *pdbentry,const char *pstring);
void dbMsgPrint(DBENTRY *pdbentry, const char *fmt, ...) EPICS_PRINTF_STYLE(2,3);
void dbMsgPrint(
DBENTRY *pdbentry, EPICS_PRINTF_FMT(const char *fmt), ...
) EPICS_PRINTF_STYLE(2,3);
void dbPutStringSuggest(DBENTRY *pdbentry, const char *pstring);

View File

@@ -11,7 +11,7 @@
SRC_DIRS += $(IOCDIR)/dbtemplate
PROD_HOST += msi
PROD_CMD += msi
msi_SRCS = msi.cpp
msi_LIBS += Com

View File

@@ -216,22 +216,96 @@ registrar(tsInitialize)
=head3 TimeStamp Filter C<"ts">
This filter replaces the timestamp in the data fetched through the channel with
the time the value was fetched (or an update was sent). The record's timestamp
This filter is used for two purposes:
=over
=item * to retrieve the timestamp of the record as a value in several different
formats;
=item * to retrieve the record value as normal, but replace the timestamp with
the time the value was fetched.
=back
=head4 Parameters
=head4 No parameters (an empty pair of braces)
Retrieve the record value as normal, but replace the timestamp with the time the
value was fetched (or an update was sent). This is useful for clients that can't
handle timestamps that are far in the past. Normally, the record's timestamp
indicates when the record last processed, which could have been days or even
weeks ago for some records, or set to the EPICS epoch if the record has never
processed.
=head4 Parameters
=head4 Numeric type C<"num">
None, use an empty pair of braces.
The following values are accepted for this parameter:
=head4 Example
=over
Hal$ caget -a 'test:channel.{"ts":{}}'
test:channel.{"ts":{}} 2012-08-28 22:10:31.192547 0 UDF INVALID
Hal$ caget -a 'test:channel'
test:channel <undefined> 0 UDF INVALID
=item * C<"dbl"> requests the timestamp as C<epicsFloat64> representing the
non-integral number of seconds since epoch. This format is convenient,
but loses precision, depending on which epoch is used.
=item * C<"sec"> requests the number of seconds since epoch as C<epicsUInt32>.
=item * C<"nsec"> requests the number of nanoseconds since epoch as
C<epicsUInt32>.
=item * C<"ts"> requests the entire timestamp. It is provided as a two-element
array of C<epicsUInt32> representing seconds and nanoseconds.
=back
Note that C<epicsUInt32> cannot be transferred over Channel Access; in that
case, the value will be converted to C<epicsFloat64>.
=head4 String type C<"str">
The following values are accepted for this parameter:
=over
=item * C<"epics"> requests the timestamp as a string in the format used by
tools such as C<caget>.
=item * C<"iso"> requests the timestamp as a string in the ISO8601 format.
=back
=head4 Epoch adjustment C<"epoch">
The following values are accepted for this parameter:
=over
=item * C<"epics"> keeps the EPICS epoch (1990-01-01) and is the default if the
C<"epoch"> parameter is not specified.
=item * C<"unix"> converts the timestamp to the UNIX/POSIX epoch (1970-01-01).
=back
=head4 Examples
Hal$ caget -a 'test:invalid_ts.{"ts":{}}'
test:invalid_ts.{"ts":{}} 2012-08-28 22:10:31.192547 0 UDF INVALID
Hal$ caget -a 'test:invalid_ts'
test:invalid_ts <undefined> 0 UDF INVALID
Hal$ caget -a test:channel
test:channel 2021-03-11 18:23:48.265386 42
Hal$ caget 'test:channel.{"ts": {"str": "epics"}}'
test:channel.{"ts": {"str": "epics"}} 2021-03-11 18:23:48.265386
Hal$ caget 'test:channel.{"ts": {"str": "iso"}}'
test:channel.{"ts": {"str": "iso"}} 2021-03-11T18:23:48.265386+0100
Hal$ caget -f9 'test:channel.{"ts": {"num": "dbl"}}'
test:channel.{"ts": {"num": "dbl"}} 984331428.265386105
Hal$ caget -f1 'test:channel.{"ts": {"num": "ts"}}'
test:channel.{"ts": {"num": "ts"}} 2 984331428.0 265386163.0
Hal$ caget -f1 'test:channel.{"ts": {"num": "ts", "epoch": "unix"}}'
test:channel.{"ts": {"num": "ts", "epoch": "unix"}} 2 1615483428.0 265386163.0
=cut

View File

@@ -1,4 +1,5 @@
/*************************************************************************\
* Copyright (c) 2021 Cosylab d.d
* Copyright (c) 2010 Brookhaven National Laboratory.
* Copyright (c) 2010 Helmholtz-Zentrum Berlin
* fuer Materialien und Energie GmbH.
@@ -9,6 +10,7 @@
/*
* Author: Ralph Lange <Ralph.Lange@bessy.de>
* Author: Jure Varlec <jure.varlec@cosylab.com>
*/
#include <stdio.h>
@@ -16,20 +18,124 @@
#include <string.h>
#include "chfPlugin.h"
#include "db_field_log.h"
#include "dbExtractArray.h"
#include "dbLock.h"
#include "db_field_log.h"
#include "epicsExport.h"
#include "freeList.h"
#include "errlog.h"
/*
* The size of the data is different for each channel, and can even
* change at runtime, so a freeList doesn't make much sense here.
*/
/* Allocation size for freelists */
#define ALLOC_NUM_ELEMENTS 32
#define logicErrorMessage() \
errMessage(-1, "Logic error: invalid state encountered in ts filter")
/* Filter settings */
enum tsMode {
tsModeInvalid = 0,
tsModeGenerate = 1,
tsModeDouble = 2,
tsModeSec = 3,
tsModeNsec = 4,
tsModeArray = 5,
tsModeString = 6,
};
static const chfPluginEnumType ts_numeric_enum[] = {
{"dbl", 2}, {"sec", 3}, {"nsec", 4}, {"ts", 5}};
enum tsEpoch {
tsEpochEpics = 0,
tsEpochUnix = 1,
};
static const chfPluginEnumType ts_epoch_enum[] = {{"epics", 0}, {"unix", 1}};
enum tsString {
tsStringInvalid = 0,
tsStringEpics = 1,
tsStringIso = 2,
};
static const chfPluginEnumType ts_string_enum[] = {{"epics", 1}, {"iso", 2}};
typedef struct tsPrivate {
enum tsMode mode;
enum tsEpoch epoch;
enum tsString str;
} tsPrivate;
static const chfPluginArgDef ts_args[] = {
chfEnum(tsPrivate, mode, "num", 0, 0, ts_numeric_enum),
chfEnum(tsPrivate, epoch, "epoch", 0, 0, ts_epoch_enum),
chfEnum(tsPrivate, str, "str", 0, 0, ts_string_enum),
chfPluginArgEnd
};
static int parse_finished(void *pvt) {
tsPrivate *settings = (tsPrivate *)pvt;
if (settings->str != tsStringInvalid) {
settings->mode = tsModeString;
#if defined _MSC_VER && _MSC_VER <= 1700
// VS 2012 crashes in ISO mode, doesn't support timezones
if (settings->str == tsStringIso) {
return -1;
}
#endif
} else if (settings->mode == tsModeInvalid) {
settings->mode = tsModeGenerate;
}
return 0;
}
/* Allocation of filter settings */
static void *private_free_list;
static void * allocPvt() {
return freeListCalloc(private_free_list);
}
static void freePvt(void *pvt) {
freeListFree(private_free_list, pvt);
}
/* Allocation of two-element arrays for second+nanosecond pairs */
static void *ts_array_free_list;
static void *allocTsArray() {
return freeListCalloc(ts_array_free_list);
}
static void freeTsArray(db_field_log *pfl) {
freeListFree(ts_array_free_list, pfl->u.r.field);
}
/* Allocation of strings */
static void *string_free_list;
static void *allocString() {
return freeListCalloc(string_free_list);
}
static void freeString(db_field_log *pfl) {
freeListFree(string_free_list, pfl->u.r.field);
}
/* The dtor for waveform data for the case when we have to copy it. */
static void freeArray(db_field_log *pfl) {
/*
* The size of the data is different for each channel, and can even
* change at runtime, so a freeList doesn't make much sense here.
*/
free(pfl->u.r.field);
}
static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) {
static db_field_log* generate(void* pvt, dbChannel *chan, db_field_log *pfl) {
epicsTimeStamp now;
epicsTimeGetCurrent(&now);
@@ -44,7 +150,7 @@ static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) {
dbScanLock(dbChannelRecord(chan));
dbChannelGetArrayInfo(chan, &pSource, &nSource, &offset);
dbExtractArray(pSource, pTarget, pfl->field_size,
nSource, pfl->no_elements, offset, 1);
nSource, pfl->no_elements, offset, 1);
pfl->u.r.field = pTarget;
pfl->dtor = freeArray;
pfl->u.r.pvt = pvt;
@@ -56,34 +162,246 @@ static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) {
return pfl;
}
static void channelRegisterPre(dbChannel *chan, void *pvt,
chPostEventFunc **cb_out, void **arg_out, db_field_log *probe)
{
static db_field_log *replace_fl_value(tsPrivate const *pvt,
db_field_log *pfl,
int (*func)(tsPrivate const *,
db_field_log *)) {
/* Get rid of the old value */
if (pfl->type == dbfl_type_ref && pfl->dtor) {
pfl->dtor(pfl);
pfl->dtor = NULL;
}
pfl->no_elements = 1;
pfl->type = dbfl_type_val;
if (func(pvt, pfl)) {
db_delete_field_log(pfl);
pfl = NULL;
}
return pfl;
}
static void ts_to_array(tsPrivate const *settings,
epicsTimeStamp const *ts,
epicsUInt32 arr[2]) {
arr[0] = ts->secPastEpoch;
arr[1] = ts->nsec;
if (settings->epoch == tsEpochUnix) {
/* Cannot use epicsTimeToWhatever because Whatever uses signed ints */
arr[0] += POSIX_TIME_AT_EPICS_EPOCH;
}
}
static int ts_seconds(tsPrivate const *settings, db_field_log *pfl) {
epicsUInt32 arr[2];
ts_to_array(settings, &pfl->time, arr);
pfl->field_type = DBF_ULONG;
pfl->field_size = sizeof(epicsUInt32);
pfl->u.v.field.dbf_ulong = arr[0];
return 0;
}
static int ts_nanos(tsPrivate const *settings, db_field_log *pfl) {
epicsUInt32 arr[2];
ts_to_array(settings, &pfl->time, arr);
pfl->field_type = DBF_ULONG;
pfl->field_size = sizeof(epicsUInt32);
pfl->u.v.field.dbf_ulong = arr[1];
return 0;
}
static int ts_double(tsPrivate const *settings, db_field_log *pfl) {
epicsUInt32 arr[2];
ts_to_array(settings, &pfl->time, arr);
pfl->field_type = DBF_DOUBLE;
pfl->field_size = sizeof(epicsFloat64);
pfl->u.v.field.dbf_double = arr[0] + arr[1] * 1e-9;
return 0;
}
static int ts_array(tsPrivate const *settings, db_field_log *pfl) {
pfl->field_type = DBF_ULONG;
pfl->field_size = sizeof(epicsUInt32);
pfl->type = dbfl_type_ref;
pfl->u.r.pvt = NULL;
pfl->u.r.field = allocTsArray();
if (pfl->u.r.field) {
pfl->no_elements = 2;
pfl->dtor = freeTsArray;
ts_to_array(settings, &pfl->time, (epicsUInt32*)pfl->u.r.field);
} else {
pfl->no_elements = 0;
pfl->dtor = NULL;
}
return 0;
}
static int ts_string(tsPrivate const *settings, db_field_log *pfl) {
char const *fmt;
char *field;
size_t n;
switch (settings->str) {
case tsStringEpics:
fmt = "%Y-%m-%d %H:%M:%S.%06f";
break;
case tsStringIso:
fmt = "%Y-%m-%dT%H:%M:%S.%06f%z";
break;
case tsStringInvalid:
default:
logicErrorMessage();
return 1;
}
pfl->field_type = DBF_STRING;
pfl->field_size = MAX_STRING_SIZE;
pfl->type = dbfl_type_ref;
pfl->u.r.pvt = NULL;
pfl->u.r.field = allocString();
if (!pfl->u.r.field) {
pfl->no_elements = 0;
pfl->dtor = NULL;
return 0;
}
pfl->dtor = freeString;
field = (char *)pfl->u.r.field;
n = epicsTimeToStrftime(field, MAX_STRING_SIZE, fmt, &pfl->time);
if (!n) {
field[0] = 0;
}
return 0;
}
static db_field_log *filter(void *pvt, dbChannel *chan, db_field_log *pfl) {
tsPrivate *settings = (tsPrivate *)pvt;
(void)chan;
switch (settings->mode) {
case tsModeDouble:
return replace_fl_value(pvt, pfl, ts_double);
case tsModeSec:
return replace_fl_value(pvt, pfl, ts_seconds);
case tsModeNsec:
return replace_fl_value(pvt, pfl, ts_nanos);
case tsModeArray:
return replace_fl_value(pvt, pfl, ts_array);
case tsModeString:
return replace_fl_value(pvt, pfl, ts_string);
case tsModeGenerate:
case tsModeInvalid:
default:
logicErrorMessage();
db_delete_field_log(pfl);
pfl = NULL;
}
return pfl;
}
/* Only the "generate" mode is registered for the pre-queue chain as it creates
it's own timestamp which should be as close to the event as possible */
static void channelRegisterPre(dbChannel * chan, void *pvt,
chPostEventFunc **cb_out, void **arg_out,
db_field_log *probe) {
tsPrivate *settings = (tsPrivate *)pvt;
(void)chan;
(void)arg_out;
(void)probe;
*cb_out = settings->mode == tsModeGenerate ? generate : NULL;
}
/* For other modes, the post-chain is fine as they only manipulate existing
timestamps */
static void channelRegisterPost(dbChannel *chan, void *pvt,
chPostEventFunc **cb_out, void **arg_out,
db_field_log *probe) {
tsPrivate *settings = (tsPrivate *)pvt;
(void)chan;
if (settings->mode == tsModeGenerate || settings->mode == tsModeInvalid) {
*cb_out = NULL;
return;
}
*cb_out = filter;
*arg_out = pvt;
/* Get rid of the value of the probe because we will be changing the
datatype */
if (probe->type == dbfl_type_ref && probe->dtor) {
probe->dtor(probe);
probe->dtor = NULL;
}
probe->no_elements = 1;
probe->type = dbfl_type_val;
switch (settings->mode) {
case tsModeArray:
probe->no_elements = 2;
/* fallthrough */
case tsModeSec:
case tsModeNsec:
probe->field_type = DBF_ULONG;
probe->field_size = sizeof(epicsUInt32);
break;
case tsModeDouble:
probe->field_type = DBF_DOUBLE;
probe->field_size = sizeof(epicsFloat64);
break;
case tsModeString:
probe->field_type = DBF_STRING;
probe->field_size = MAX_STRING_SIZE;
break;
case tsModeGenerate:
case tsModeInvalid:
// Already handled above, added here for completeness.
default:
logicErrorMessage();
*cb_out = NULL;
}
}
static void channel_report(dbChannel *chan, void *pvt, int level, const unsigned short indent)
{
printf("%*sTimestamp (ts)\n", indent, "");
tsPrivate *settings = (tsPrivate *)pvt;
(void)chan;
(void)level;
printf("%*sTimestamp (ts): mode: %d, epoch: %d, str: %d\n",
indent, "", settings->mode, settings->epoch, settings->str);
}
static chfPluginIf pif = {
NULL, /* allocPvt, */
NULL, /* freePvt, */
allocPvt,
freePvt,
NULL, /* parse_error, */
NULL, /* parse_ok, */
NULL, /* parse_error, */
parse_finished,
NULL, /* channel_open, */
channelRegisterPre,
NULL, /* channelRegisterPost, */
channelRegisterPost,
channel_report,
NULL /* channel_close */
};
static void tsInitialize(void)
{
chfPluginRegister("ts", &pif, NULL);
freeListInitPvt(&private_free_list, sizeof(tsPrivate),
ALLOC_NUM_ELEMENTS);
freeListInitPvt(&ts_array_free_list, 2 * sizeof(epicsUInt32),
ALLOC_NUM_ELEMENTS);
freeListInitPvt(&string_free_list, MAX_STRING_SIZE,
ALLOC_NUM_ELEMENTS);
chfPluginRegister("ts", &pif, ts_args);
}
epicsExportRegistrar(tsInitialize);

View File

@@ -42,6 +42,8 @@ value is non-zero.
=back
This record type was included in base.dbd beginning with epics-base 3.14.10 .
=head2 Record-specific Menus
=head3 Menu aSubLFLG

View File

@@ -460,12 +460,17 @@ conditions.
The HYST field defines an alarm deadband for each limit.
The AFTC field sets the time constant on a low-pass filter that delays the
reporting of limit alarms until the signal has been within the alarm range for
that number of seconds (the default AFTC value of zero retains the previous
behavior).
See L<Alarm Specification|https://docs.epics-controls.org/en/latest/guides/EPICS_Process_Database_Concepts.html#alarm-specification>
for a complete explanation of record alarms and of the standard fields.
L<Alarm Fields|dbCommonRecord/Alarm Fields> lists other fields related
to alarms that are common to all record types.
=fields HIHI, HIGH, LOW, LOLO, HHSV, HSV, LSV, LLSV, HYST
=fields HIHI, HIGH, LOW, LOLO, HHSV, HSV, LSV, LLSV, HYST, AFTC
=head3 Monitor Parameters
@@ -496,7 +501,7 @@ manner for the VAL field.
=cut
include "dbCommon.dbd"
include "dbCommon.dbd"
field(VAL,DBF_DOUBLE) {
prompt("Result")
promptgroup("50 - Output")

View File

@@ -13,6 +13,8 @@ The histogram record is used to store frequency counts of a signal into an array
of arbitrary length. The user can configure the range of the signal value that
the array will store. Anything outside this range will be ignored.
This record type was included in base.dbd beginning with epics-base 3.15.0.1 .
=head2 Parameter Fields
The record-specific fields are described below.

View File

@@ -14,6 +14,8 @@ from a hardware input.
The record supports alarm limits, alarm filtering, graphics and control
limits.
This record type was included in base.dbd beginning with epics-base 3.16.1 .
=head2 Parameter Fields
The record-specific fields are described below.

View File

@@ -13,6 +13,8 @@ This record type is normally used to send an integer value of up to 64 bits
to an output device.
The record supports alarm, drive, graphics and control limits.
This record type was included in base.dbd beginning with epics-base 3.16.1 .
=head2 Parameter Fields
The record-specific fields are described below.

View File

@@ -70,6 +70,15 @@ The possible alarm conditions for long inputs are the SCAN, READ, and limit
alarms. The SCAN and READ alarms are called by the record or device support
routines.
The HYST field controls hysteresis to prevent alarm chattering from an input
signal that is close to one of the limits and suffers from significant readout
noise.
The AFTC field sets the time constant on a low-pass filter that delays the
reporting of limit alarms until the signal has been within the alarm range for
that number of seconds (the default AFTC value of zero retains the previous
behavior).
The limit alarms are configured by the user in the HIHI, LOLO, HIGH, and LOW
fields using numerical values. For each of these fields, there is a
corresponding severity field which can be either NO_ALARM, MINOR, or MAJOR. The
@@ -77,7 +86,7 @@ HYST field can be used to specify a deadband around each limit.
L<Alarm Fields|dbCommonRecord/Alarm Fields> lists the fields related to
alarms that are common to all record types.
=fields HIHI, HIGH, LOW, LOLO, HHSV, HSV, LSV, LLSV, HYST
=fields HIHI, HIGH, LOW, LOLO, HHSV, HSV, LSV, LLSV, HYST, AFTC
=head3 Monitor Parameters

View File

@@ -11,6 +11,8 @@
The long string input record is used to retrieve an arbitrary ASCII string with
a maximum length of 65535 characters.
This record type was included in base.dbd beginning with epics-base 3.15.0.2 .
=head2 Parameter Fields
The record-specific fields are described below, grouped by functionality.

View File

@@ -11,6 +11,8 @@
The long string output record is used to write an arbitrary ASCII string with a
maximum length of 65535 characters.
This record type was included in base.dbd beginning with epics-base 3.15.0.2 .
=head2 Parameter Fields
The record-specific fields are described below, grouped by functionality.

View File

@@ -405,6 +405,11 @@ and state alarms. The state alarms are configured in the below severity fields.
These fields have the usual possible values for severity fields: NO_ALARM,
MINOR, and MAJOR.
The AFTC field sets the time constant on a low-pass filter that delays the
reporting of limit alarms until the signal has been within the alarm range for
that number of seconds (the default AFTC value of zero retains the previous
behavior).
The unknown state severity (UNSV) field, if set to MINOR or MAJOR, triggers an
alarm when the record support routine cannot find a matching value in the state
value fields for C<<< rval >>>.
@@ -420,7 +425,7 @@ for a complete explanation of record alarms and of the standard fields.
L<Alarm Fields|dbCommonRecord/Alarm Fields> lists other fields related
to alarms that are common to all record types.
=fields UNSV, COSV, ZRSV, ONSV, TWSV, THSV, FRSV, FVSV, SXSV, SVSV, EISV, NISV, TESV, ELSV, TVSV, TTSV, FTSV, FFSV
=fields UNSV, COSV, ZRSV, ONSV, TWSV, THSV, FRSV, FVSV, SXSV, SVSV, EISV, NISV, TESV, ELSV, TVSV, TTSV, FTSV, FFSV, AFTC
=cut

View File

@@ -169,7 +169,7 @@ static void doPrintf(printfRecord *prec)
precision = 0;
if (ch == '%') {
added = epicsSnprintf(pval, vspace + 1, "%s", format);
added = epicsSnprintf(pval, vspace + 1, format);
}
else if (linkn++ >= PRINTF_NLINKS) {
/* No more LNKn fields */

View File

@@ -11,6 +11,8 @@
The printf record is used to generate and write a string using a format
specification and parameters, analogous to the C C<printf()> function.
This record type was included in base.dbd beginning with epics-base 3.15.0.2 .
=head2 Parameter Fields
The record-specific fields are described below, grouped by functionality.

View File

@@ -3,7 +3,7 @@ To start the ioc from this directory execute the command
Alternatively make the st.cmd file directly executable with
chmod +x st.cmd
and check the executable name on the first line of the st.cmd file
and check the executable name on the first line of the st.cmd file
You may need to change the name of the .dbd file given in the
st.cmd's dbLoadDatabase() command before starting the ioc.

View File

@@ -181,6 +181,8 @@ dbStaticTest_SRCS += dbStaticTest.c
dbStaticTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp
testHarness_SRCS += dbStaticTest.c
TESTFILES += ../dbStaticTest.db
TESTFILES += ../dbStaticTestAlias1.db
TESTFILES += ../dbStaticTestAlias2.db
TESTS += dbStaticTest
# This runs all the test programs in a known working order:

View File

@@ -291,6 +291,18 @@ static void testDbVerify(const char *record)
dbFinishEntry(&entry);
}
static void testWrongAliasRecord(const char *filename)
{
FILE *fp = NULL;
dbPath(pdbbase,"." OSI_PATH_LIST_SEPARATOR "..");
dbOpenFile(pdbbase, filename, &fp);
if(!fp) {
testAbort("Unable to read %s", filename);
}
testOk(dbReadDatabaseFP(&pdbbase, fp, NULL, NULL) != 0,
"Wrong alias record in %s is expected to fail", filename);
}
void dbTestIoc_registerRecordDeviceDriver(struct dbBase *);
MAIN(dbStaticTest)
@@ -298,13 +310,14 @@ MAIN(dbStaticTest)
const char *ldir;
FILE *fp = NULL;
testPlan(310);
testPlan(312);
testdbPrepare();
testdbReadDatabase("dbTestIoc.dbd", NULL, NULL);
dbTestIoc_registerRecordDeviceDriver(pdbbase);
dbPath(pdbbase,"." OSI_PATH_LIST_SEPARATOR "..");
if(!(ldir = dbOpenFile(pdbbase, "dbStaticTest.db", &fp))) {
ldir = dbOpenFile(pdbbase, "dbStaticTest.db", &fp);
if(!fp) {
testAbort("Unable to read dbStaticTest.db");
}
if(dbReadDatabaseFP(&pdbbase, fp, NULL, NULL)) {
@@ -312,6 +325,9 @@ MAIN(dbStaticTest)
ldir, OSI_PATH_LIST_SEPARATOR);
}
testWrongAliasRecord("dbStaticTestAlias1.db");
testWrongAliasRecord("dbStaticTestAlias2.db");
testEntry("testrec.VAL");
testEntry("testalias.VAL");
testEntry("testalias2.VAL");

View File

@@ -0,0 +1,4 @@
record(x, "testrec2alias") {
}
alias("testrec2alias.NAME", "testalias4")

View File

@@ -0,0 +1,4 @@
record(x, "testrec2alias2") {
}
alias("testrec2alias2", "testalias5.NAME")

View File

@@ -1,4 +1,5 @@
/*************************************************************************\
* Copyright (c) 2021 Cosylab d.d
* Copyright (c) 2010 Brookhaven National Laboratory.
* Copyright (c) 2010 Helmholtz-Zentrum Berlin
* fuer Materialien und Energie GmbH.
@@ -9,9 +10,11 @@
/*
* Author: Ralph Lange <Ralph.Lange@bessy.de>
* Author: Jure Varlec <jure.varlec@cosylab.com>
*/
#include <string.h>
#include <math.h>
#include "dbStaticLib.h"
#include "dbAccessDefs.h"
@@ -25,11 +28,24 @@
#include "testMain.h"
#include "osiFileName.h"
/* A fill pattern for setting a field log to something "random". */
#define PATTERN 0x55
void filterTest_registerRecordDeviceDriver(struct dbBase *);
/* Use a "normal" timestamp for testing. What results from filling the field log
with the above pattern is a timestamp that causes problems with
epicsTimeToStrftime() on some platforms. */
static epicsTimeStamp const test_ts = { 616600420, 998425354 };
static db_field_log fl;
typedef int (*TypeCheck)(const db_field_log *pfl);
typedef int (*ValueCheck)(const db_field_log *pfl, const epicsTimeStamp *ts);
typedef struct {
char const *channel;
TypeCheck type_check;
ValueCheck value_check;
} TestSpec;
void filterTest_registerRecordDeviceDriver(struct dbBase *);
static int fl_equal(const db_field_log *pfl1, const db_field_log *pfl2) {
return !(memcmp(pfl1, pfl2, sizeof(db_field_log)));
@@ -42,21 +58,210 @@ static int fl_equal_ex_ts(const db_field_log *pfl1, const db_field_log *pfl2) {
return fl_equal(&fl1, pfl2);
}
MAIN(tsTest)
{
static void fl_reset(db_field_log *pfl) {
memset(pfl, PATTERN, sizeof(*pfl));
pfl->time = test_ts;
}
static void test_generate_filter(const chFilterPlugin *plug) {
dbChannel *pch;
chFilter *filter;
const chFilterPlugin *plug;
char ts[] = "ts";
ELLNODE *node;
chPostEventFunc *cb_out = NULL;
void *arg_out = NULL;
db_field_log fl;
db_field_log fl1;
db_field_log *pfl2;
epicsTimeStamp stamp, now;
dbEventCtx evtctx;
ELLNODE *node;
chPostEventFunc *cb_out = NULL;
void *arg_out = NULL;
char const *chan_name = "x.VAL{ts:{}}";
testPlan(12);
testOk(!!(pch = dbChannelCreate(chan_name)),
"dbChannel with plugin ts created");
testOk((ellCount(&pch->filters) == 1), "channel has one plugin");
fl_reset(&fl);
fl1 = fl;
node = ellFirst(&pch->filters);
filter = CONTAINER(node, chFilter, list_node);
plug->fif->channel_register_post(filter, &cb_out, &arg_out, &fl1);
plug->fif->channel_register_pre(filter, &cb_out, &arg_out, &fl1);
testOk(!!(cb_out) && !(arg_out),
"register_pre registers one filter w/o argument");
testOk(fl_equal(&fl1, &fl),
"register_pre does not change field_log data type");
testOk(!(dbChannelOpen(pch)), "dbChannel with plugin ts opened");
node = ellFirst(&pch->pre_chain);
filter = CONTAINER(node, chFilter, pre_node);
testOk((ellCount(&pch->pre_chain) == 1 && filter->pre_arg == NULL),
"ts has one filter w/o argument in pre chain");
testOk((ellCount(&pch->post_chain) == 0), "ts has no filter in post chain");
fl_reset(&fl);
fl1 = fl;
pfl2 = dbChannelRunPreChain(pch, &fl1);
testOk(pfl2 == &fl1, "ts filter does not drop or replace field_log");
testOk(fl_equal_ex_ts(&fl1, pfl2),
"ts filter does not change field_log data");
testOk(!!(pfl2 = db_create_read_log(pch)), "create field log from channel");
stamp = pfl2->time;
db_delete_field_log(pfl2);
pfl2 = dbChannelRunPreChain(pch, &fl1);
epicsTimeGetCurrent(&now);
testOk(epicsTimeDiffInSeconds(&pfl2->time, &stamp) >= 0 &&
epicsTimeDiffInSeconds(&now, &pfl2->time) >= 0,
"ts filter sets time stamp to \"now\"");
dbChannelDelete(pch);
}
static void test_value_filter(const chFilterPlugin *plug, const char *chan_name,
TypeCheck tc_func, ValueCheck vc_func) {
dbChannel *pch;
chFilter *filter;
db_field_log fl;
db_field_log fl2;
db_field_log *pfl;
epicsTimeStamp ts;
ELLNODE *node;
chPostEventFunc *cb_out = NULL;
void *arg_out = NULL;
testDiag("Channel %s", chan_name);
testOk(!!(pch = dbChannelCreate(chan_name)),
"dbChannel with plugin ts created");
testOk((ellCount(&pch->filters) == 1), "channel has one plugin");
fl_reset(&fl);
fl.type = dbfl_type_val;
node = ellFirst(&pch->filters);
filter = CONTAINER(node, chFilter, list_node);
plug->fif->channel_register_pre(filter, &cb_out, &arg_out, &fl);
plug->fif->channel_register_post(filter, &cb_out, &arg_out, &fl);
testOk(!!(cb_out) && arg_out,
"register_post registers one filter with argument");
testOk(tc_func(&fl), "register_post gives correct field type");
testOk(!(dbChannelOpen(pch)), "dbChannel with plugin ts opened");
node = ellFirst(&pch->post_chain);
filter = CONTAINER(node, chFilter, post_node);
testOk((ellCount(&pch->post_chain) == 1 && filter->post_arg != NULL),
"ts has one filter with argument in post chain");
testOk((ellCount(&pch->pre_chain) == 0), "ts has no filter in pre chain");
fl_reset(&fl);
fl.type = dbfl_type_val;
ts = fl.time;
fl2 = fl;
pfl = dbChannelRunPostChain(pch, &fl);
testOk(pfl == &fl, "ts filter does not drop or replace field_log");
testOk(tc_func(pfl), "ts filter gives correct field type");
testOk((pfl->time.secPastEpoch == fl2.time.secPastEpoch &&
pfl->time.nsec == fl2.time.nsec && pfl->stat == fl2.stat &&
pfl->sevr == fl2.sevr),
"ts filter does not touch non-value fields of field_log");
testOk(vc_func(pfl, &ts), "ts filter gives correct field value");
dbChannelDelete(pch);
}
static int type_check_double(const db_field_log *pfl) {
return pfl->type == dbfl_type_val
&& pfl->field_type == DBR_DOUBLE
&& pfl->field_size == sizeof(epicsFloat64)
&& pfl->no_elements == 1;
}
static int value_check_double(const db_field_log *pfl, const epicsTimeStamp *ts) {
epicsFloat64 flt = pfl->u.v.field.dbf_double;
epicsFloat64 nsec = (flt - (epicsUInt32)(flt)) * 1e9;
return ts->secPastEpoch == (epicsUInt32)(flt)
&& fabs(ts->nsec - nsec) < 1000.; /* allow loss of precision */
}
static int type_check_sec_nsec(const db_field_log *pfl) {
return pfl->type == dbfl_type_val
&& pfl->field_type == DBR_ULONG
&& pfl->field_size == sizeof(epicsUInt32)
&& pfl->no_elements == 1;
}
static int value_check_sec(const db_field_log *pfl, const epicsTimeStamp *ts) {
return ts->secPastEpoch == pfl->u.v.field.dbf_ulong;
}
static int value_check_nsec(const db_field_log *pfl, const epicsTimeStamp *ts) {
return ts->nsec == pfl->u.v.field.dbf_ulong;
}
static int type_check_array(const db_field_log *pfl) {
return pfl->field_type == DBR_ULONG
&& pfl->field_size == sizeof(epicsUInt32)
&& pfl->no_elements == 2;
}
static int value_check_array(const db_field_log *pfl, const epicsTimeStamp *ts) {
epicsUInt32 *arr = (epicsUInt32*)pfl->u.r.field;
return pfl->type == dbfl_type_ref
&& pfl->u.r.field != NULL
&& pfl->dtor != NULL
&& pfl->u.r.pvt == NULL
&& ts->secPastEpoch == arr[0]
&& ts->nsec == arr[1];
}
static int value_check_unix(const db_field_log *pfl, const epicsTimeStamp *ts) {
epicsUInt32 *arr = (epicsUInt32 *)pfl->u.r.field;
return pfl->type == dbfl_type_ref
&& pfl->u.r.field != NULL
&& pfl->dtor != NULL
&& pfl->u.r.pvt == NULL
&& ts->secPastEpoch == arr[0] - POSIX_TIME_AT_EPICS_EPOCH
&& ts->nsec == arr[1];
}
static int type_check_string(const db_field_log *pfl) {
return pfl->field_type == DBR_STRING
&& pfl->field_size == MAX_STRING_SIZE
&& pfl->no_elements == 1;
}
static int value_check_string(const db_field_log *pfl, const epicsTimeStamp *ts) {
/* We can only verify the type, not the value, because using strptime()
might be problematic. */
(void)ts;
return pfl->type == dbfl_type_ref
&& pfl->u.r.field != NULL
&& pfl->dtor != NULL
&& pfl->u.r.pvt == NULL;
}
MAIN(tsTest) {
int i;
char ts[] = "ts";
dbEventCtx evtctx;
const chFilterPlugin *plug;
static TestSpec const tests[] = {
{"x.VAL{ts:{\"num\": \"dbl\"}}", type_check_double, value_check_double},
{"x.VAL{ts:{\"num\": \"sec\"}}", type_check_sec_nsec, value_check_sec},
{"x.VAL{ts:{\"num\": \"nsec\"}}", type_check_sec_nsec, value_check_nsec},
{"x.VAL{ts:{\"num\": \"ts\"}}", type_check_array, value_check_array},
{"x.VAL{ts:{\"num\": \"ts\", \"epoch\": \"epics\"}}", type_check_array, value_check_array},
{"x.VAL{ts:{\"num\": \"ts\", \"epoch\": \"unix\"}}", type_check_array, value_check_unix},
{"x.VAL{ts:{\"str\": \"epics\"}}", type_check_string, value_check_string},
#if !(defined _MSC_VER && _MSC_VER <= 1700)
{"x.VAL{ts:{\"str\": \"iso\"}}", type_check_string, value_check_string},
#endif
};
static int const num_value_tests = sizeof(tests) / sizeof(tests[0]);
testPlan(12 /* test_generate_filter() */
+ num_value_tests * 11 /* test_value_filter() */);
testdbPrepare();
@@ -77,41 +282,12 @@ MAIN(tsTest)
testAbort("plugin '%s' not registered", ts);
testPass("plugin '%s' registered correctly", ts);
testOk(!!(pch = dbChannelCreate("x.VAL{ts:{}}")), "dbChannel with plugin ts created");
testOk((ellCount(&pch->filters) == 1), "channel has one plugin");
test_generate_filter(plug);
memset(&fl, PATTERN, sizeof(fl));
fl1 = fl;
node = ellFirst(&pch->filters);
filter = CONTAINER(node, chFilter, list_node);
plug->fif->channel_register_pre(filter, &cb_out, &arg_out, &fl1);
testOk(!!(cb_out) && !(arg_out), "register_pre registers one filter w/o argument");
testOk(fl_equal(&fl1, &fl), "register_pre does not change field_log data type");
testOk(!(dbChannelOpen(pch)), "dbChannel with plugin ts opened");
node = ellFirst(&pch->pre_chain);
filter = CONTAINER(node, chFilter, pre_node);
testOk((ellCount(&pch->pre_chain) == 1 && filter->pre_arg == NULL),
"ts has one filter w/o argument in pre chain");
testOk((ellCount(&pch->post_chain) == 0), "ts has no filter in post chain");
memset(&fl, PATTERN, sizeof(fl));
fl1 = fl;
pfl2 = dbChannelRunPreChain(pch, &fl1);
testOk(pfl2 == &fl1, "ts filter does not drop or replace field_log");
testOk(fl_equal_ex_ts(&fl1, pfl2), "ts filter does not change field_log data");
testOk(!!(pfl2 = db_create_read_log(pch)), "create field log from channel");
stamp = pfl2->time;
db_delete_field_log(pfl2);
pfl2 = dbChannelRunPreChain(pch, &fl1);
epicsTimeGetCurrent(&now);
testOk(epicsTimeDiffInSeconds(&pfl2->time, &stamp) >= 0 &&
epicsTimeDiffInSeconds(&now, &pfl2->time) >= 0,
"ts filter sets time stamp to \"now\"");
dbChannelDelete(pch);
for (i = 0; i < num_value_tests; ++i) {
TestSpec const *t = &tests[i];
test_value_filter(plug, t->channel, t->type_check, t->value_check);
}
db_close_events(evtctx);

View File

@@ -23,6 +23,11 @@ PROD_LIBS = dbRecStdTest dbRecStd dbCore ca Com
TARGETS += $(COMMON_DIR)/recTestIoc.dbd
DBDDEPENDS_FILES += recTestIoc.dbd$(DEP)
recTestIoc_DBD = base.dbd
recTestIoc_DBD += bptTypeKdegC.dbd
recTestIoc_DBD += bptTypeKdegF.dbd
recTestIoc_DBD += bptTypeJdegF.dbd
recTestIoc_DBD += bptTypeJdegC.dbd
TESTFILES += $(COMMON_DIR)/recTestIoc.dbd
testHarness_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
@@ -93,6 +98,34 @@ testHarness_SRCS += longoutTest.c
TESTFILES += ../longoutTest.db
TESTS += longoutTest
TESTPROD_HOST += boTest
boTest_SRCS += boTest.c
boTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
testHarness_SRCS += boTest.c
TESTFILES += ../boTest.db
TESTS += boTest
TESTPROD_HOST += biTest
biTest_SRCS += biTest.c
biTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
testHarness_SRCS += biTest.c
TESTFILES += ../biTest.db
TESTS += biTest
TESTPROD_HOST += printfTest
printfTest_SRCS += printfTest.c
printfTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
testHarness_SRCS += printfTest.c
TESTFILES += ../printfTest.db
TESTS += printfTest
TESTPROD_HOST += aiTest
aiTest_SRCS += aiTest.c
aiTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
testHarness_SRCS += aiTest.c
TESTFILES += ../aiTest.db
TESTS += aiTest
TARGETS += $(COMMON_DIR)/asTestIoc.dbd
DBDDEPENDS_FILES += asTestIoc.dbd$(DEP)
asTestIoc_DBD += base.dbd

View File

@@ -0,0 +1,445 @@
/*************************************************************************\
* Copyright (c) 2023 Karl Vestin
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
#include "dbUnitTest.h"
#include "testMain.h"
#include "errlog.h"
#include "dbAccess.h"
#include "menuAlarmSevr.h"
#include "menuConvert.h"
#include "epicsMath.h"
#include "menuScan.h"
#include "caeventmask.h"
void recTestIoc_registerRecordDeviceDriver(struct dbBase *);
static void test_soft_input(void){
double value = 543.123;
/* set soft channel */
testdbPutFieldOk("test_ai_rec.DTYP", DBF_STRING, "Soft Channel");
testdbPutFieldOk("test_ai_rec.INP", DBF_STRING, "test_ai_link_rec.VAL");
testdbPutFieldOk("test_ai_link_rec.FLNK", DBF_STRING, "test_ai_rec");
/* set VAL to on linked record */
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_DOUBLE, value);
/* verify that this record VAL is updated but RVAL is not */
testdbGetFieldEqual("test_ai_rec.VAL", DBF_DOUBLE, value);
testdbGetFieldEqual("test_ai_rec.RVAL", DBF_DOUBLE, 0.0);
// number of tests = 6
}
static void test_raw_soft_input(void){
double value = -255;
/* set soft channel */
testdbPutFieldOk("test_ai_rec.DTYP", DBF_STRING, "Raw Soft Channel");
testdbPutFieldOk("test_ai_rec.INP", DBF_STRING, "test_ai_link_rec.VAL");
testdbPutFieldOk("test_ai_link_rec.FLNK", DBF_STRING, "test_ai_rec");
/* set VAL to on linked record */
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_DOUBLE, value);
/* verify that this record RVAL and VAL are updated */
testdbGetFieldEqual("test_ai_rec.VAL", DBF_DOUBLE, value);
testdbGetFieldEqual("test_ai_rec.RVAL", DBF_DOUBLE, value);
// number of tests = 6
}
static void test_operator_display(void){
const short hopr = 199;
const short lopr = 50;
const short prec = 2;
const char egu[] = "mm";
const char desc[] = "hmm?";
/* set operator display parameters */
testdbPutFieldOk("test_ai_rec.EGU", DBF_STRING, egu);
testdbPutFieldOk("test_ai_rec.DESC", DBF_STRING, desc);
testdbPutFieldOk("test_ai_rec.HOPR", DBF_SHORT, hopr);
testdbPutFieldOk("test_ai_rec.LOPR", DBF_SHORT, lopr);
testdbPutFieldOk("test_ai_rec.PREC", DBF_SHORT, prec);
/* verify operator display parameters read back */
testdbGetFieldEqual("test_ai_rec.NAME", DBF_STRING, "test_ai_rec");
testdbGetFieldEqual("test_ai_rec.EGU", DBF_STRING, egu);
testdbGetFieldEqual("test_ai_rec.DESC", DBF_STRING, desc);
testdbGetFieldEqual("test_ai_rec.HOPR", DBF_SHORT, hopr);
testdbGetFieldEqual("test_ai_rec.LOPR", DBF_SHORT, lopr);
testdbGetFieldEqual("test_ai_rec.PREC", DBF_SHORT, prec);
// number of tests = 7
}
static void test_no_linr_unit_conversion(void){
const short roff = 10;
const short aslo = 2;
const short aoff = 4;
const short rval = 9;
const short val = (((rval + roff) * aslo) + aoff);
/* set soft channel */
testdbPutFieldOk("test_ai_rec.DTYP", DBF_STRING, "Raw Soft Channel");
testdbPutFieldOk("test_ai_rec.INP", DBF_STRING, "test_ai_link_rec.VAL");
testdbPutFieldOk("test_ai_link_rec.FLNK", DBF_STRING, "test_ai_rec");
/* set unit conversion parameters */
testdbPutFieldOk("test_ai_rec.ROFF", DBF_SHORT, roff);
testdbPutFieldOk("test_ai_rec.ASLO", DBF_SHORT, aslo);
testdbPutFieldOk("test_ai_rec.AOFF", DBF_SHORT, aoff);
testdbPutFieldOk("test_ai_rec.LINR", DBF_LONG, menuConvertNO_CONVERSION);
/* verify conversion */
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_SHORT, rval);
testdbGetFieldEqual("test_ai_rec.VAL", DBF_SHORT, val);
testdbGetFieldEqual("test_ai_rec.RVAL", DBF_SHORT, rval);
// number of tests = 10
}
static void test_slope_linr_unit_conversion(void){
const short roff = 1;
const short aslo = 3;
const short aoff = 99;
const short eslo = 3;
const short eoff = 2;
const short rval = 7;
const short val = ((((rval + roff) * aslo) + aoff) * eslo) + eoff;
/* set soft channel */
testdbPutFieldOk("test_ai_rec.DTYP", DBF_STRING, "Raw Soft Channel");
testdbPutFieldOk("test_ai_rec.INP", DBF_STRING, "test_ai_link_rec.VAL");
testdbPutFieldOk("test_ai_link_rec.FLNK", DBF_STRING, "test_ai_rec");
/* set unit conversion parameters */
testdbPutFieldOk("test_ai_rec.ROFF", DBF_LONG, roff);
testdbPutFieldOk("test_ai_rec.ASLO", DBF_LONG, aslo);
testdbPutFieldOk("test_ai_rec.AOFF", DBF_LONG, aoff);
testdbPutFieldOk("test_ai_rec.ESLO", DBF_LONG, eslo);
testdbPutFieldOk("test_ai_rec.EOFF", DBF_LONG, eoff);
testdbPutFieldOk("test_ai_rec.LINR", DBF_LONG, menuConvertSLOPE);
/* verify conversion */
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_LONG, rval);
testdbGetFieldEqual("test_ai_rec.VAL", DBF_LONG, val);
testdbGetFieldEqual("test_ai_rec.RVAL", DBF_LONG, rval);
// number of tests = 12
}
static void test_linear_linr_unit_conversion(void){
const long roff = 6;
const long aslo = 2;
const long aoff = 19;
const long eslo = 4;
const long eoff = 1;
const long egul = 9999;
const long eguf = -1000;
const long rval = 2;
/* Since our raw soft input does not support EGUL and EGUF conversion LINEAR should work like SLOPE */
const long val = ((((rval + roff) * aslo) + aoff) * eslo) + eoff;
/* set soft channel */
testdbPutFieldOk("test_ai_rec.DTYP", DBF_STRING, "Raw Soft Channel");
testdbPutFieldOk("test_ai_rec.INP", DBF_STRING, "test_ai_link_rec.VAL");
testdbPutFieldOk("test_ai_link_rec.FLNK", DBF_STRING, "test_ai_rec");
/* set unit conversion parameters */
testdbPutFieldOk("test_ai_rec.ROFF", DBF_LONG, roff);
testdbPutFieldOk("test_ai_rec.ASLO", DBF_LONG, aslo);
testdbPutFieldOk("test_ai_rec.AOFF", DBF_LONG, aoff);
testdbPutFieldOk("test_ai_rec.ESLO", DBF_LONG, eslo);
testdbPutFieldOk("test_ai_rec.EOFF", DBF_LONG, eoff);
/* Since our raw soft input does not support EGUL and EGUF conversion we set them to lagre values here just to check they dont break anything */
testdbPutFieldOk("test_ai_rec.EGUL", DBF_LONG, egul);
testdbPutFieldOk("test_ai_rec.EGUF", DBF_LONG, eguf);
testdbPutFieldOk("test_ai_rec.LINR", DBF_LONG, menuConvertLINEAR);
/* verify conversion */
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_LONG, rval);
testdbGetFieldEqual("test_ai_rec.VAL", DBF_LONG, val);
testdbGetFieldEqual("test_ai_rec.RVAL", DBF_LONG, rval);
// number of tests = 14
}
static void test_bpt_conversion(void){
const float typeKdegFin = 699.550510;
const float typeKdegF = 3.427551e+02;
const float typeKdegCin = 2902.787322;
const float typeKdegC = 7.028131e+02;
const float typeJdegFin = 1218.865107;
const float typeJdegF = 3.897504e+02;
const float typeJdegCin = 4042.988372;
const float typeJdegC = 6.918437e+02;
/* set soft channel */
testdbPutFieldOk("test_ai_rec.DTYP", DBF_STRING, "Raw Soft Channel");
testdbPutFieldOk("test_ai_rec.INP", DBF_STRING, "test_ai_link_rec.VAL");
testdbPutFieldOk("test_ai_link_rec.FLNK", DBF_STRING, "test_ai_rec");
/* set unit conversion parameters */
testdbPutFieldOk("test_ai_rec.ROFF", DBF_LONG, 0);
testdbPutFieldOk("test_ai_rec.ASLO", DBF_LONG, 1);
testdbPutFieldOk("test_ai_rec.AOFF", DBF_LONG, 0);
/* Set type K deg F break point table and verify */
testdbPutFieldOk("test_ai_rec.LINR", DBF_SHORT, menuConverttypeKdegF);
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_FLOAT, typeKdegFin);
testdbGetFieldEqual("test_ai_rec.VAL", DBF_FLOAT, typeKdegF);
/* Set type K deg C break point table and verify */
testdbPutFieldOk("test_ai_rec.LINR", DBF_SHORT, menuConverttypeKdegC);
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_FLOAT, typeKdegCin);
testdbGetFieldEqual("test_ai_rec.VAL", DBF_FLOAT, typeKdegC);
/* Set type J deg F break point table and verify */
testdbPutFieldOk("test_ai_rec.LINR", DBF_SHORT, menuConverttypeJdegF);
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_FLOAT, typeJdegFin);
testdbGetFieldEqual("test_ai_rec.VAL", DBF_FLOAT, typeJdegF);
/* Set type J deg C break point table and verify */
testdbPutFieldOk("test_ai_rec.LINR", DBF_SHORT, menuConverttypeJdegC);
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_FLOAT, typeJdegCin);
testdbGetFieldEqual("test_ai_rec.VAL", DBF_FLOAT, typeJdegC);
// number of tests = 18
}
static void test_smoothing_filter(void){
const double smoo = 0.7;
const short roff = 1;
const short aslo = 3;
const short aoff = -2;
const short eslo = 2;
const short eoff = 6;
const short rval1 = 7;
const short val1 = ((((rval1 + roff) * aslo) + aoff) * eslo) + eoff;
const short rval2 = 9;
const short val2_pre_smoo = ((((rval2 + roff) * aslo) + aoff) * eslo) + eoff;
const float val2 = val1 * smoo + (1 - smoo) * val2_pre_smoo;
/* set soft channel */
testdbPutFieldOk("test_ai_rec.DTYP", DBF_STRING, "Raw Soft Channel");
testdbPutFieldOk("test_ai_rec.INP", DBF_STRING, "test_ai_link_rec.VAL");
testdbPutFieldOk("test_ai_link_rec.FLNK", DBF_STRING, "test_ai_rec");
/* set unit conversion parameters */
testdbPutFieldOk("test_ai_rec.ROFF", DBF_LONG, roff);
testdbPutFieldOk("test_ai_rec.ASLO", DBF_LONG, aslo);
testdbPutFieldOk("test_ai_rec.AOFF", DBF_LONG, aoff);
testdbPutFieldOk("test_ai_rec.ESLO", DBF_LONG, eslo);
testdbPutFieldOk("test_ai_rec.EOFF", DBF_LONG, eoff);
testdbPutFieldOk("test_ai_rec.LINR", DBF_LONG, menuConvertSLOPE);
/* set well known starting point (without smmothing) */
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_SHORT, rval1);
testdbGetFieldEqual("test_ai_rec.VAL", DBF_SHORT, val1);
/* set SMOO */
testdbPutFieldOk("test_ai_rec.SMOO", DBF_DOUBLE, smoo);
/* update value again and verify smoothing */
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_SHORT, rval2);
testdbGetFieldEqual("test_ai_rec.VAL", DBF_FLOAT, val2);
/* clear SMOO */
testdbPutFieldOk("test_ai_rec.SMOO", DBF_DOUBLE, 0.0);
// number of tests = 15
}
static void test_udf(void){
/* set soft channel */
testdbPutFieldOk("test_ai_rec2.DTYP", DBF_STRING, "Raw Soft Channel");
testdbPutFieldOk("test_ai_rec2.INP", DBF_STRING, "test_ai_link_rec2.VAL");
testdbPutFieldOk("test_ai_link_rec2.FLNK", DBF_STRING, "test_ai_rec2");
/* verify UDF */
testdbGetFieldEqual("test_ai_rec2.UDF", DBF_CHAR, TRUE);
/* set VAL and verify UDF is cleared */
testdbPutFieldOk("test_ai_link_rec2.VAL", DBF_FLOAT, 7.0);
testdbGetFieldEqual("test_ai_rec2.UDF", DBF_CHAR, FALSE);
// number of tests = 6
}
static void test_alarm(void){
const double h = 5000;
const double hh = 7500;
const double l = 200;
const double ll = -20;
const double hyst = 5;
/* set soft channel */
testdbPutFieldOk("test_ai_rec.DTYP", DBF_STRING, "Soft Channel");
testdbPutFieldOk("test_ai_rec.INP", DBF_STRING, "test_ai_link_rec.VAL");
testdbPutFieldOk("test_ai_link_rec.FLNK", DBF_STRING, "test_ai_rec");
/* set alarm parameters */
testdbPutFieldOk("test_ai_rec.HIGH", DBF_DOUBLE, h);
testdbPutFieldOk("test_ai_rec.HIHI", DBF_DOUBLE, hh);
testdbPutFieldOk("test_ai_rec.LOW", DBF_DOUBLE, l);
testdbPutFieldOk("test_ai_rec.LOLO", DBF_DOUBLE, ll);
testdbPutFieldOk("test_ai_rec.HHSV", DBF_SHORT, menuAlarmSevrMAJOR);
testdbPutFieldOk("test_ai_rec.HSV", DBF_SHORT, menuAlarmSevrMINOR);
testdbPutFieldOk("test_ai_rec.LSV", DBF_SHORT, menuAlarmSevrMINOR);
testdbPutFieldOk("test_ai_rec.LLSV", DBF_SHORT, menuAlarmSevrMAJOR);
/* set non alarm VAL and verify */
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_DOUBLE, 400.0);
testdbGetFieldEqual("test_ai_rec.SEVR", DBF_SHORT, menuAlarmSevrNO_ALARM);
/* set LOW alarm VAL and verify */
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_DOUBLE, 190.0);
testdbGetFieldEqual("test_ai_rec.VAL", DBF_DOUBLE, 190.0);
testdbGetFieldEqual("test_ai_rec.SEVR", DBF_SHORT, menuAlarmSevrMINOR);
/* set LOLO alarm VAL and verify */
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_DOUBLE, -9998.0);
testdbGetFieldEqual("test_ai_rec.SEVR", DBF_SHORT, menuAlarmSevrMAJOR);
/* set HIGH alarm VAL and verify */
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_DOUBLE, 6111.0);
testdbGetFieldEqual("test_ai_rec.SEVR", DBF_SHORT, menuAlarmSevrMINOR);
/* set HIHI alarm VAL and verify */
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_DOUBLE, 19998.0);
testdbGetFieldEqual("test_ai_rec.SEVR", DBF_SHORT, menuAlarmSevrMAJOR);
/* set HYST, verify and clear */
testdbPutFieldOk("test_ai_rec.HYST", DBF_DOUBLE, hyst);
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_DOUBLE, hh - 1.0);
testdbGetFieldEqual("test_ai_rec.SEVR", DBF_SHORT, menuAlarmSevrMAJOR);
testdbPutFieldOk("test_ai_rec.HYST", DBF_DOUBLE, 0.0);
testdbPutFieldOk("test_ai_link_rec.VAL", DBF_DOUBLE, hh - 2.0);
testdbGetFieldEqual("test_ai_rec.SEVR", DBF_SHORT, menuAlarmSevrMINOR);
/* verify LALM */
testdbGetFieldEqual("test_ai_rec.LALM", DBF_DOUBLE, h);
// number of tests = 29
}
static void test_aftc(void){
const double h = 5000;
const double hh = 7500;
const double l = 200;
const double ll = -20;
const double aftc = 3;
testMonitor* test_mon = NULL;
epicsTimeStamp startTime;
epicsTimeStamp endTime;
double diffTime = 0;
/* set soft channel */
testdbPutFieldOk("test_ai_rec3.DTYP", DBF_STRING, "Raw Soft Channel");
testdbPutFieldOk("test_ai_rec3.INP", DBF_STRING, "test_ai_link_rec3.VAL");
testdbPutFieldOk("test_ai_link_rec3.FLNK", DBF_STRING, "test_ai_rec3");
/* set alarm parameters */
testdbPutFieldOk("test_ai_rec3.HIGH", DBF_DOUBLE, h);
testdbPutFieldOk("test_ai_rec3.HIHI", DBF_DOUBLE, hh);
testdbPutFieldOk("test_ai_rec3.LOW", DBF_DOUBLE, l);
testdbPutFieldOk("test_ai_rec3.LOLO", DBF_DOUBLE, ll);
testdbPutFieldOk("test_ai_rec3.HHSV", DBF_SHORT, menuAlarmSevrMAJOR);
testdbPutFieldOk("test_ai_rec3.HSV", DBF_SHORT, menuAlarmSevrMINOR);
testdbPutFieldOk("test_ai_rec3.LSV", DBF_SHORT, menuAlarmSevrMINOR);
testdbPutFieldOk("test_ai_rec3.LLSV", DBF_SHORT, menuAlarmSevrMAJOR);
/* test AFTC using a monitor and time stamps */
testdbPutFieldOk("test_ai_rec3.AFTC", DBF_DOUBLE, aftc);
testdbPutFieldOk("test_ai_rec3.SCAN", DBF_SHORT, menuScan_1_second);
/* set HIHI alarm VAL */
testdbPutFieldOk("test_ai_link_rec3.VAL", DBF_DOUBLE, 7550.0);
/* Create test monitor for alarm SEVR */
test_mon = testMonitorCreate("test_ai_rec3.VAL", DBE_ALARM, 0);
/* Get start time */
epicsTimeGetCurrent(&startTime);
/* wait for monitor to trigger on the new alarm status*/
testMonitorWait(test_mon);
epicsTimeGetCurrent(&endTime);
/* Verify that alarm status is now MAJOR */
testdbGetFieldEqual("test_ai_rec3.SEVR", DBF_SHORT, menuAlarmSevrMAJOR);
/* set HI alarm VAL */
testdbPutFieldOk("test_ai_link_rec3.VAL", DBF_DOUBLE, 5550.0);
/* Create test monitor for alarm SEVR */
test_mon = testMonitorCreate("test_ai_rec3.VAL", DBE_ALARM, 0);
/* Get start time */
epicsTimeGetCurrent(&startTime);
/* wait for monitor to trigger on the new alarm status*/
testMonitorWait(test_mon);
epicsTimeGetCurrent(&endTime);
/* Verify that alarm status is now MINOR */
testdbGetFieldEqual("test_ai_rec3.SEVR", DBF_SHORT, menuAlarmSevrMINOR);
/* Verify that time is at least equal to configured aftc */
diffTime = epicsTimeDiffInSeconds(&endTime, &startTime);
testOk(diffTime >= aftc, "ATFC time %lf", diffTime);
// number of tests = 18
}
MAIN(aiTest) {
#ifdef _WIN32
#if (defined(_MSC_VER) && _MSC_VER < 1900) || \
(defined(_MINGW) && defined(_TWO_DIGIT_EXPONENT))
_set_output_format(_TWO_DIGIT_EXPONENT);
#endif
#endif
testPlan(6+6+11+10+12+14+18+15+6+29+18);
testdbPrepare();
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
recTestIoc_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("aiTest.db", NULL, NULL);
eltc(0);
testIocInitOk();
eltc(1);
test_soft_input();
test_raw_soft_input();
test_operator_display();
test_no_linr_unit_conversion();
test_slope_linr_unit_conversion();
test_linear_linr_unit_conversion();
test_bpt_conversion();
test_smoothing_filter();
test_udf();
test_alarm();
test_aftc();
testIocShutdownOk();
testdbCleanup();
return testDone();
}

View File

@@ -0,0 +1,20 @@
record(ai, "test_ai_rec") {
}
record(ao, "test_ai_link_rec") {
}
record(ai, "test_ai_rec2") {
}
record(ao, "test_ai_link_rec2") {
}
record(ai, "test_ai_rec3") {
}
record(ao, "test_ai_link_rec3") {
}

View File

@@ -0,0 +1,122 @@
/*************************************************************************\
* Copyright (c) 2023 Karl Vestin
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
#include "dbUnitTest.h"
#include "testMain.h"
#include "errlog.h"
#include "dbAccess.h"
#include "menuAlarmSevr.h"
void recTestIoc_registerRecordDeviceDriver(struct dbBase *);
static void test_soft_input(void){
/* set soft channel */
testdbPutFieldOk("test_bi_rec.DTYP", DBF_STRING, "Soft Channel");
testdbPutFieldOk("test_bi_rec.INP", DBF_STRING, "test_bi_link_rec.VAL");
testdbPutFieldOk("test_bi_link_rec.FLNK", DBF_STRING, "test_bi_rec");
/* set VAL to on linked record */
testdbPutFieldOk("test_bi_link_rec.VAL", DBF_SHORT, TRUE);
/* verify that this record VAL is updated but RVAL is not */
testdbGetFieldEqual("test_bi_rec.VAL", DBF_SHORT, TRUE);
testdbGetFieldEqual("test_bi_rec.RVAL", DBF_SHORT, FALSE);
// number of tests = 6
}
static void test_raw_soft_input(void){
/* set soft channel */
testdbPutFieldOk("test_bi_rec.DTYP", DBF_STRING, "Raw Soft Channel");
testdbPutFieldOk("test_bi_rec.INP", DBF_STRING, "test_bi_link_rec.VAL");
testdbPutFieldOk("test_bi_link_rec.FLNK", DBF_STRING, "test_bi_rec");
/* set VAL to on linked record */
testdbPutFieldOk("test_bi_link_rec.VAL", DBF_SHORT, TRUE);
/* verify that this record RVAL and VAL are updated */
testdbGetFieldEqual("test_bi_rec.VAL", DBF_SHORT, TRUE);
testdbGetFieldEqual("test_bi_rec.RVAL", DBF_SHORT, TRUE);
// number of tests = 6
}
static void test_operator_display(void){
/* set operator display parameters */
testdbPutFieldOk("test_bi_rec.ZNAM", DBF_STRING, "ZNAM_TEST");
testdbPutFieldOk("test_bi_rec.ONAM", DBF_STRING, "ONAM_TEST");
testdbPutFieldOk("test_bi_rec.DESC", DBF_STRING, "DESC_TEST");
/* verify operator display parameters */
testdbGetFieldEqual("test_bi_rec.ZNAM", DBF_STRING, "ZNAM_TEST");
testdbGetFieldEqual("test_bi_rec.ONAM", DBF_STRING, "ONAM_TEST");
testdbGetFieldEqual("test_bi_rec.NAME", DBF_STRING, "test_bi_rec");
testdbGetFieldEqual("test_bi_rec.DESC", DBF_STRING, "DESC_TEST");
/* verify conversion */
testdbPutFieldOk("test_bi_link_rec.VAL", DBF_SHORT, TRUE);
testdbGetFieldEqual("test_bi_rec.VAL", DBF_STRING, "ONAM_TEST");
testdbPutFieldOk("test_bi_link_rec.VAL", DBF_SHORT, FALSE);
testdbGetFieldEqual("test_bi_rec.VAL", DBF_STRING, "ZNAM_TEST");
// number of tests = 11
}
static void test_alarm(void){
/* set soft channel */
testdbPutFieldOk("test_bi_rec.DTYP", DBF_STRING, "Soft Channel");
testdbPutFieldOk("test_bi_rec.INP", DBF_STRING, "test_bi_link_rec.VAL");
testdbPutFieldOk("test_bi_link_rec.FLNK", DBF_STRING, "test_bi_rec");
/* set start VAL to FALSE*/
testdbPutFieldOk("test_bi_link_rec.VAL", DBF_SHORT, FALSE);
/* set alarm parameters */
testdbPutFieldOk("test_bi_rec.ZSV", DBF_SHORT, menuAlarmSevrNO_ALARM);
testdbPutFieldOk("test_bi_rec.OSV", DBF_SHORT, menuAlarmSevrMINOR);
testdbPutFieldOk("test_bi_rec.COSV", DBF_SHORT, menuAlarmSevrINVALID);
/* verify alarm status is NO_ALARM*/
testdbGetFieldEqual("test_bi_rec.SEVR", DBF_SHORT, menuAlarmSevrNO_ALARM);
/* set ZSV to MAJOR and verify that SEVR is now MAJOR */
testdbPutFieldOk("test_bi_rec.ZSV", DBF_SHORT, menuAlarmSevrMAJOR);
testdbGetFieldEqual("test_bi_rec.SEVR", DBF_SHORT, menuAlarmSevrMAJOR);
/* set VAL to 1 on linked record and verify that COSV now sets the SEVR to INVALID */
testdbPutFieldOk("test_bi_link_rec.VAL", DBF_SHORT, TRUE);
testdbGetFieldEqual("test_bi_rec.SEVR", DBF_SHORT, menuAlarmSevrINVALID);
/* verify LAML */
testdbGetFieldEqual("test_bi_rec.LALM", DBF_SHORT, TRUE);
// number of tests = 13
}
MAIN(biTest) {
testPlan(6+6+11+13);
testdbPrepare();
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
recTestIoc_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("biTest.db", NULL, NULL);
eltc(0);
testIocInitOk();
eltc(1);
test_soft_input();
test_raw_soft_input();
test_operator_display();
test_alarm();
testIocShutdownOk();
testdbCleanup();
return testDone();
}

View File

@@ -0,0 +1,6 @@
record(bi, "test_bi_rec") {
}
record(bo, "test_bi_link_rec") {
}

View File

@@ -0,0 +1,158 @@
/*************************************************************************\
* Copyright (c) 2023 Karl Vestin
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
#include "dbUnitTest.h"
#include "testMain.h"
#include "errlog.h"
#include "dbAccess.h"
#include "menuAlarmSevr.h"
#include "menuIvoa.h"
#include "boRecord.h"
void recTestIoc_registerRecordDeviceDriver(struct dbBase *);
static void test_soft_output(void){
/* set soft channel */
testdbPutFieldOk("test_bo_rec.DTYP", DBF_STRING, "Soft Channel");
testdbPutFieldOk("test_bo_rec.OUT", DBF_STRING, "test_bo_link_rec");
/* set VAL to process record */
testdbPutFieldOk("test_bo_rec.VAL", DBF_SHORT, TRUE);
/* verify that OUT record is updated */
testdbGetFieldEqual("test_bo_link_rec.VAL", DBF_SHORT, TRUE);
// number of tests = 4
}
static void test_high(void){
const int high_time = 2;
testMonitor* test_mon = NULL;
epicsTimeStamp startTime;
epicsTimeStamp endTime;
double diffTime = 0;
double diffTimeTolerance = 0.1;
/* set soft channel */
testdbPutFieldOk("test_bo_rec.DTYP", DBF_STRING, "Soft Channel");
testdbPutFieldOk("test_bo_rec.OUT", DBF_STRING, "test_bo_link_rec");
/* set HIGH to 2 seconds */
testdbPutFieldOk("test_bo_rec.HIGH", DBF_SHORT, high_time);
/* Create test monitor */
test_mon = testMonitorCreate("test_bo_rec.VAL", DBR_SHORT, 0);
/* Get start time */
epicsTimeGetCurrent(&startTime);
/* set VAL to process record */
testdbPutFieldOk("test_bo_rec.VAL", DBF_SHORT, TRUE);
/* wait and verfiy time */
testMonitorWait(test_mon);
epicsTimeGetCurrent(&endTime);
/* EPICS timers have a tendency to trip slightly early, hence the test tolerance is added here to avoid false positives in testing */
diffTime = epicsTimeDiffInSeconds(&endTime, &startTime) + diffTimeTolerance;
testOk(diffTime >= high_time, "HIGH time %lf", diffTime);
/* verify that both records are set back to 0 */
testdbGetFieldEqual("test_bo_rec.VAL", DBF_SHORT, FALSE);
testdbGetFieldEqual("test_bo_link_rec.VAL", DBF_SHORT, FALSE);
testMonitorDestroy(test_mon);
// number of tests = 7
}
static void test_operator_display(void){
/* set operator display parameters */
testdbPutFieldOk("test_bo_rec.ZNAM", DBF_STRING, "ZNAM_TEST");
testdbPutFieldOk("test_bo_rec.ONAM", DBF_STRING, "ONAM_TEST");
testdbPutFieldOk("test_bo_rec.DESC", DBF_STRING, "DESC_TEST");
/* verify operator display parameters */
testdbGetFieldEqual("test_bo_rec.ZNAM", DBF_STRING, "ZNAM_TEST");
testdbGetFieldEqual("test_bo_rec.ONAM", DBF_STRING, "ONAM_TEST");
testdbGetFieldEqual("test_bo_rec.NAME", DBF_STRING, "test_bo_rec");
testdbGetFieldEqual("test_bo_rec.DESC", DBF_STRING, "DESC_TEST");
/* verify conversion */
testdbPutFieldOk("test_bo_rec.VAL", DBF_SHORT, TRUE);
testdbGetFieldEqual("test_bo_rec.VAL", DBF_STRING, "ONAM_TEST");
testdbPutFieldOk("test_bo_rec.VAL", DBF_SHORT, FALSE);
testdbGetFieldEqual("test_bo_rec.VAL", DBF_STRING, "ZNAM_TEST");
// number of tests = 11
}
static void test_alarm(void){
/* set soft channel */
testdbPutFieldOk("test_bo_rec.DTYP", DBF_STRING, "Soft Channel");
testdbPutFieldOk("test_bo_rec.OUT", DBF_STRING, "test_bo_link_rec");
/* Set start VAL to FALSE*/
testdbPutFieldOk("test_bo_rec.VAL", DBF_SHORT, FALSE);
/* set alarm parameters */
testdbPutFieldOk("test_bo_rec.ZSV", DBF_SHORT, menuAlarmSevrNO_ALARM);
testdbPutFieldOk("test_bo_rec.OSV", DBF_SHORT, menuAlarmSevrMINOR);
testdbPutFieldOk("test_bo_rec.COSV", DBF_SHORT, menuAlarmSevrINVALID);
testdbPutFieldOk("test_bo_rec.IVOA", DBF_SHORT, menuIvoaSet_output_to_IVOV);
testdbPutFieldOk("test_bo_rec.IVOV", DBF_SHORT, FALSE);
/* Verify alarm status is NO_ALARM*/
testdbGetFieldEqual("test_bo_rec.SEVR", DBF_SHORT, menuAlarmSevrNO_ALARM);
/* Set ZSV to MAJOR and verify that SEVR is now MAJOR */
testdbPutFieldOk("test_bo_rec.ZSV", DBF_SHORT, menuAlarmSevrMAJOR);
testdbGetFieldEqual("test_bo_rec.SEVR", DBF_SHORT, menuAlarmSevrMAJOR);
/* Set VAL to 1 and verify that COSV now sets the SEVR to INVALID and in turn triggers the IVOV on output */
testdbPutFieldOk("test_bo_link_rec.VAL", DBF_SHORT, TRUE);
testdbPutFieldOk("test_bo_rec.VAL", DBF_SHORT, TRUE);
testdbGetFieldEqual("test_bo_rec.SEVR", DBF_SHORT, menuAlarmSevrINVALID);
testdbGetFieldEqual("test_bo_link_rec.VAL", DBF_SHORT, FALSE);
/* verify IVOV continue normally action */
testdbPutFieldOk("test_bo_rec.IVOA", DBF_SHORT, menuIvoaContinue_normally);
testdbPutFieldOk("test_bo_rec.VAL", DBF_SHORT, TRUE);
testdbGetFieldEqual("test_bo_link_rec.VAL", DBF_SHORT, TRUE);
/* verify IVOV dont drive outputs action */
testdbPutFieldOk("test_bo_rec.VAL", DBF_SHORT, FALSE);
testdbPutFieldOk("test_bo_rec.IVOA", DBF_SHORT, menuIvoaDon_t_drive_outputs);
testdbPutFieldOk("test_bo_rec.VAL", DBF_SHORT, TRUE);
testdbGetFieldEqual("test_bo_link_rec.VAL", DBF_SHORT, FALSE);
// number of tests = 22
}
MAIN(boTest) {
testPlan(4+7+11+22);
testdbPrepare();
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
recTestIoc_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("boTest.db", NULL, NULL);
eltc(0);
testIocInitOk();
eltc(1);
test_soft_output();
test_high();
test_operator_display();
test_alarm();
testIocShutdownOk();
testdbCleanup();
return testDone();
}

View File

@@ -0,0 +1,6 @@
record(bo, "test_bo_rec") {
}
record(bi, "test_bo_link_rec") {
}

View File

@@ -24,6 +24,10 @@ int asyncSoftTest(void);
int simmTest(void);
int mbbioDirectTest(void);
int scanEventTest(void);
int boTest(void);
int biTest(void);
int printfTest(void);
int aiTest(void);
void epicsRunRecordTests(void)
{
@@ -51,5 +55,13 @@ void epicsRunRecordTests(void)
runTest(scanEventTest);
runTest(boTest);
runTest(biTest);
runTest(printfTest);
runTest(aiTest);
epicsExit(0); /* Trigger test harness */
}

View File

@@ -0,0 +1,577 @@
/*************************************************************************\
* Copyright (c) 2023 Karl Vestin
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
#include "dbUnitTest.h"
#include "testMain.h"
#include "errlog.h"
#include "dbAccess.h"
void recTestIoc_registerRecordDeviceDriver(struct dbBase *);
static void test_double_percentile(void){
const char format_string[] = "Format test string %%d";
const char result_string[] = "Format test string %d";
/* set format string */
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
/* set value on inp0 */
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 1);
/* verify that string is formatted as expected */
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
// number of tests = 3
}
static void test_d_format(void){
const char format_string[] = "Format test string %d";
const char result_string[] = "Format test string 7";
/* set format string */
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
/* set value on inp0 */
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 7);
/* verify that string is formatted as expected */
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
// number of tests = 3
}
static void test_c_format(void){
const char format_string[] = "Format test string %c";
const char result_string[] = "Format test string R";
/* set format string */
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
/* set value on inp0, 82 is ASCII for R */
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 82);
/* verify that string is formatted as expected */
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
// number of tests = 3
}
static void test_i_format(void){
const char format_string[] = "Format test string %i";
const char result_string[] = "Format test string -27";
/* set format string */
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
/* set value on inp0 */
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, -27);
/* verify that string is formatted as expected */
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
// number of tests = 3
}
static void test_o_format(void){
const char format_string[] = "Format test string %o";
const char result_string[] = "Format test string 6777";
/* set format string */
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
/* set value on inp0 */
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 06777);
/* verify that string is formatted as expected */
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
// number of tests = 3
}
static void test_u_format(void){
const char format_string[] = "Format test string %u";
const char result_string[] = "Format test string 8009";
/* set format string */
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
/* set value on inp0 */
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 8009);
/* verify that string is formatted as expected */
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
// number of tests = 3
}
static void test_x_format(void){
const char format_string[] = "Format test string %x";
const char result_string[] = "Format test string fafa";
/* set format string */
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
/* set value on inp0 */
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_LONG, 0xfafa);
/* verify that string is formatted as expected */
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
// number of tests = 3
}
static void test_X_format(void){
const char format_string[] = "Format test string %X";
const char result_string[] = "Format test string BA";
/* set format string */
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
/* set value on inp0 */
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 0x00ba);
/* verify that string is formatted as expected */
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
// number of tests = 3
}
static void test_e_format(void){
const char format_string[] = "Format test string %e";
const char result_string[] = "Format test string -1.400000e+01";
/* set format string */
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
/* set value on inp0 */
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, -14);
/* verify that string is formatted as expected */
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
// number of tests = 3
}
static void test_E_format(void){
const char format_string[] = "Format test string %E";
const char result_string[] = "Format test string 1.992000E+03";
/* set format string */
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
/* set value on inp0 */
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 1992);
/* verify that string is formatted as expected */
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
// number of tests = 3
}
static void test_f_format(void){
const char format_string[] = "Format test string %f";
const char result_string[] = "Format test string -0.062000";
/* set format string */
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
/* set value on inp0 */
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_DOUBLE, -0.062);
/* verify that string is formatted as expected */
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
// number of tests = 3
}
static void test_F_format(void){
const char format_string[] = "Format test string %F";
const char result_string[] = "Format test string 6729982.999000";
/* set format string */
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
/* set value on inp0 */
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_DOUBLE, 6729982.999);
/* verify that string is formatted as expected */
// visual studio less than 2015 does not support %F
// mingw/gcc also fails, suspect this may be gcc version
// related and checking __GNUC__ could resolve but
// initial attempts didn't work so excluding mingw entirely for now
#ifdef _WIN32
#if (defined(_MSC_VER) && _MSC_VER < 1900) || defined(_MINGW)
testTodoBegin("Fails on windows with old visual studio versions and mingw");
#endif
#endif
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
#ifdef _WIN32
#if (defined(_MSC_VER) && _MSC_VER < 1900) || defined(_MINGW)
testTodoEnd();
#endif
#endif
// number of tests = 3
}
static void test_g_format(void){
const char format_string[] = "Format test string %g";
const char result_string[] = "Format test string -0.093";
/* set format string */
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
/* set value on inp0 */
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_DOUBLE, -93e-3);
/* verify that string is formatted as expected */
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
// number of tests = 3
}
static void test_G_format(void){
const char format_string[] = "Format test string %G";
const char result_string[] = "Format test string 7.2884E+08";
/* set format string */
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
/* set value on inp0 */
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_LONG, 728839938);
/* verify that string is formatted as expected */
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
// number of tests = 3
}
static void test_s_format(void){
const char format_string[] = "Format test string %d %s";
const char result_string[] = "Format test string 7 Molly III";
/* set format string */
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
/* set value on inp0 */
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 7);
/* set value on inp1 */
testdbPutFieldOk("test_printf_inp1_rec.VAL", DBF_STRING, "Molly III");
/* verify that string is formatted as expected */
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
// number of tests = 4
}
static void test_plus_flag(void){
const char format_string[] = "Format test string %+d";
const char result_string[] = "Format test string +7";
/* set format string */
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
/* set value on inp0 */
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 7);
/* verify that string is formatted as expected */
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
// number of tests = 3
}
static void test_minus_flag(void){
const char format_string[] = "Format test string %-10d";
const char result_string[] = "Format test string 18 ";
/* set format string */
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
/* set value on inp0 */
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 18);
/* verify that string is formatted as expected */
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
// number of tests = 3
}
static void test_space_flag(void){
const char format_string[] = "Format test string % d";
const char result_string[] = "Format test string 12";
/* set format string */
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
/* set value on inp0 */
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 12);
/* verify that string is formatted as expected */
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
// number of tests = 3
}
static void test_hash_flag(void){
const char format_string[] = "Format test string %#o";
const char result_string[] = "Format test string 014";
/* set format string */
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
/* set value on inp0 */
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 014);
/* verify that string is formatted as expected */
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
// number of tests = 3
}
static void test_min_width_flag(void){
const char format_string[] = "Format test string %04i";
const char result_string[] = "Format test string 0003";
/* set format string */
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
/* set value on inp0 */
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 3);
/* verify that string is formatted as expected */
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
// number of tests = 3
}
static void test_prec_flag(void){
const char format_string[] = "Format test string %.4f";
const char result_string[] = "Format test string 71.2000";
/* set format string */
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
/* set value on inp0 */
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_DOUBLE, 71.2);
/* verify that string is formatted as expected */
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
// number of tests = 3
}
static void test_h_flag(void){
const char format_string[] = "Format test string %hx";
const char result_string[] = "Format test string baba";
/* set format string */
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
/* set value on inp0 */
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_LONG, 0xffbaba);
/* verify that string is formatted as expected */
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
// number of tests = 3
}
static void test_hh_flag(void){
const char format_string[] = "Format test string %hhx";
const char result_string[] = "Format test string c1";
/* set format string */
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
/* set value on inp0 */
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_LONG, 0xffc0c1);
/* verify that string is formatted as expected */
#ifdef __rtems__
testTodoBegin("Fails on UB-20 gcc-9 on RTEMS");
#endif
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
#ifdef __rtems__
testTodoEnd();
#endif
// number of tests = 3
}
static void test_l_flag(void){
const char format_string[] = "Format test string %lx";
const char result_string[] = "Format test string 70a1c0c1";
/* set format string */
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
/* set value on inp0 */
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_LONG, 0x70a1c0c1);
/* verify that string is formatted as expected */
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
// number of tests = 3
}
static void test_ll_flag(void){
const char format_string[] = "%d %s %llx";
const char result_string[] = "2 Reperbahn ba0110baa0a1c0c1";
const epicsInt64 value = 0xba0110baa0a1c0c1ull;
/* set format string */
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
/* set value on inp0 */
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 2);
/* set value on inp1 */
testdbPutFieldOk("test_printf_inp1_rec.VAL", DBF_STRING, "Reperbahn");
/* set value on inp2 */
testdbPutFieldOk("test_printf_inp2_rec.VAL", DBR_INT64, value);
testdbGetFieldEqual("test_printf_inp2_rec.VAL", DBR_INT64, value);
/* verify that string is formatted as expected */
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
// number of tests = 6
}
static void test_sizv(void){
const char format_string[] = "%d %s %llx";
const char result_string[] = "99 123456789012345678901234567890 6640baa0a1";
const epicsInt64 value = 0x06640baa0a1c0c1ull;
/* set format string */
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
/* set value on inp0 */
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 99);
/* set value on inp1 */
testdbPutFieldOk("test_printf_inp1_rec.VAL", DBF_STRING, "123456789012345678901234567890");
/* set value on inp2 */
testdbPutFieldOk("test_printf_inp2_rec.VAL", DBR_INT64, value);
testdbGetFieldEqual("test_printf_inp2_rec.VAL", DBR_INT64, value);
/* verify that string is formatted as expected */
testdbGetArrFieldEqual("test_printf_rec.VAL$", DBF_CHAR, 45, 45, result_string);
// number of tests = 6
}
static void test_all_inputs(void){
const char format_string[] = "%d %s %i %i %i %i %i %i %i %i";
const char result_string[] = "0 One 2 3 4 5 6 7 8 9";
/* set format string */
testdbPutFieldOk("test_printf_rec.FMT", DBF_STRING, format_string);
/* set value on inp0 */
testdbPutFieldOk("test_printf_inp0_rec.VAL", DBF_SHORT, 0);
/* set value on inp1 */
testdbPutFieldOk("test_printf_inp1_rec.VAL", DBF_STRING, "One");
/* set value on inp2 */
testdbPutFieldOk("test_printf_inp2_rec.VAL", DBF_SHORT, 2);
/* set value on inp3 */
testdbPutFieldOk("test_printf_inp3_rec.VAL", DBF_SHORT, 3);
/* set value on inp4 */
testdbPutFieldOk("test_printf_inp4_rec.VAL", DBF_SHORT, 4);
/* set value on inp5 */
testdbPutFieldOk("test_printf_inp5_rec.VAL", DBF_SHORT, 5);
/* set value on inp6 */
testdbPutFieldOk("test_printf_inp6_rec.VAL", DBF_SHORT, 6);
/* set value on inp7 */
testdbPutFieldOk("test_printf_inp7_rec.VAL", DBF_SHORT, 7);
/* set value on inp8 */
testdbPutFieldOk("test_printf_inp8_rec.VAL", DBF_SHORT, 8);
/* set value on inp9 */
testdbPutFieldOk("test_printf_inp9_rec.VAL", DBF_SHORT, 9);
/* verify that string is formatted as expected */
testdbGetFieldEqual("test_printf_rec.VAL", DBF_STRING, result_string);
// number of tests = 12
}
MAIN(printfTest) {
#ifdef _WIN32
#if (defined(_MSC_VER) && _MSC_VER < 1900) || \
(defined(_MINGW) && defined(_TWO_DIGIT_EXPONENT))
_set_output_format(_TWO_DIGIT_EXPONENT);
#endif
#endif
testPlan(3+3+3+3+3+3+3+3+4+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+6+6+12);
testdbPrepare();
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
recTestIoc_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("printfTest.db", NULL, NULL);
eltc(0);
testIocInitOk();
eltc(1);
test_double_percentile();
test_c_format();
test_d_format();
test_i_format();
test_o_format();
test_u_format();
test_x_format();
test_X_format();
test_e_format();
test_E_format();
test_f_format();
test_F_format();
test_g_format();
test_G_format();
test_s_format();
test_plus_flag();
test_minus_flag();
test_space_flag();
test_hash_flag();
test_min_width_flag();
test_prec_flag();
test_h_flag();
test_hh_flag();
test_l_flag();
test_ll_flag();
test_sizv();
test_all_inputs();
testIocShutdownOk();
testdbCleanup();
return testDone();
}

View File

@@ -0,0 +1,53 @@
record(printf, "test_printf_rec") {
field(SIZV, 45)
field(INP0, "test_printf_inp0_rec")
field(INP1, "test_printf_inp1_rec")
field(INP2, "test_printf_inp2_rec")
field(INP3, "test_printf_inp3_rec")
field(INP4, "test_printf_inp4_rec")
field(INP5, "test_printf_inp5_rec")
field(INP6, "test_printf_inp6_rec")
field(INP7, "test_printf_inp7_rec")
field(INP8, "test_printf_inp8_rec")
field(INP9, "test_printf_inp9_rec")
}
record(ai, "test_printf_inp0_rec") {
field(FLNK, "test_printf_rec")
}
record(stringin, "test_printf_inp1_rec") {
field(FLNK, "test_printf_rec")
}
record(int64in, "test_printf_inp2_rec") {
field(FLNK, "test_printf_rec")
}
record(ai, "test_printf_inp3_rec") {
field(FLNK, "test_printf_rec")
}
record(ai, "test_printf_inp4_rec") {
field(FLNK, "test_printf_rec")
}
record(ai, "test_printf_inp5_rec") {
field(FLNK, "test_printf_rec")
}
record(ai, "test_printf_inp6_rec") {
field(FLNK, "test_printf_rec")
}
record(ai, "test_printf_inp7_rec") {
field(FLNK, "test_printf_rec")
}
record(ai, "test_printf_inp8_rec") {
field(FLNK, "test_printf_rec")
}
record(ai, "test_printf_inp9_rec") {
field(FLNK, "test_printf_rec")
}

View File

@@ -60,7 +60,19 @@ extern void *POSIX_Init(void *argument);
#define CONFIGURE_APPLICATION_NEEDS_STUB_DRIVER
#define CONFIGURE_APPLICATION_NEEDS_ZERO_DRIVER
/* Max FDs cannot exceed FD_SETSIZE from newlib (64) */
/* Note: The select() system call can only be used with the first FD_SETSIZE
* File Descriptors (newlib default is 64). Beginning RTEMS 5.1, FDs are
* allocated sequentially. So changing this CONFIGURE parameter such
* that CONFIGURE_MAXIMUM_FILE_DESCRIPTORS >= FD_SETSIZE will likely
* cause applications making select() calls to fault at some point.
*
* IOC core components (libca and RSRV) do not make select() calls.
*
* Applications and driver code using poll() or other socket
* multiplexers do not share this limitation.
*
* cf. https://github.com/epics-base/epics-base/issues/300
*/
#define CONFIGURE_MAXIMUM_FILE_DESCRIPTORS 64
#define CONFIGURE_IMFS_ENABLE_MKFIFO 2

View File

@@ -29,6 +29,8 @@
#include "postfix.h"
#include "asLib.h"
#undef ECHO /* from termios.h */
int asCheckClientIP;
static epicsMutexId asLock;

View File

@@ -23,6 +23,9 @@
class SingletonUntyped {
public:
#if __cplusplus>=201103L
constexpr
#endif
SingletonUntyped () :_pInstance ( 0 ), _refCount ( 0 ) {}
# if 0
~SingletonUntyped () {
@@ -112,6 +115,9 @@ public:
epicsSingleton * _pSingleton;
};
friend class reference;
#if __cplusplus>=201103L
constexpr
#endif
epicsSingleton () {}
// mutex lock/unlock pair overhead incurred
// when either of these are called

View File

@@ -26,6 +26,7 @@ ERR_S_FILES += $(LIBCOM)/as/asLib.h
ERR_S_FILES += $(LIBCOM)/misc/epicsStdlib.h
ERR_S_FILES += $(LIBCOM)/pool/epicsThreadPool.h
ERR_S_FILES += $(LIBCOM)/error/errMdef.h
ERR_S_FILES += $(LIBCOM)/error/errSymTbl.h
ERR_S_FILES += $(TOP)/modules/database/src/ioc/db/dbAccessDefs.h
ERR_S_FILES += $(TOP)/modules/database/src/ioc/dbStatic/dbStaticLib.h

View File

@@ -19,6 +19,7 @@
#define RTN_SUCCESS(STATUS) ((STATUS)==0)
/* Module numbers start above 500 for compatibility with vxWorks errnoLib */
#define MIN_MODULE_NUM 501
/* FIXME: M_xxx values could be declared as integer variables and set
* at runtime from registration routines; the S_xxx definitions would
@@ -32,6 +33,7 @@
#define M_stdlib (504 << 16) /* EPICS Standard library */
#define M_pool (505 << 16) /* Thread pool */
#define M_time (506 << 16) /* epicsTime */
#define M_err (507 << 16) /* Error */
/* ioc */
#define M_dbAccess (511 << 16) /* Database Access Routines */

View File

@@ -23,6 +23,9 @@
#include "cantProceed.h"
#include "epicsAssert.h"
#include "epicsStdio.h"
#include "epicsTypes.h"
#include "epicsMutex.h"
#include "epicsThread.h"
#include "errMdef.h"
#include "errSymTbl.h"
#include "ellLib.h"
@@ -33,69 +36,49 @@
static epicsUInt16 errhash(long errNum);
typedef struct errnumnode {
ELLNODE node;
long errNum;
struct errnumnode *hashnode;
const char *message;
long pad;
} ERRNUMNODE;
static ELLLIST errnumlist = ELLLIST_INIT;
static ERRNUMNODE **hashtable;
static int initialized = 0;
typedef struct {
ERRNUMNODE *table[NHASH * sizeof(ERRNUMNODE *)];
epicsMutexId tableMutexId;
} errHashTable_t;
static errHashTable_t errHashTable;
extern ERRSYMTAB_ID errSymTbl;
/****************************************************************
* ERRSYMBLD
*
* Create the normal ell LIST of sorted error messages nodes
* Followed by linked hash lists - that link together those
* ell nodes that have a common hash number.
* Populate EPICS error symbols
*
***************************************************************/
int errSymBld(void)
{
ERRSYMBOL *errArray = errSymTbl->symbols;
ERRNUMNODE *perrNumNode = NULL;
ERRNUMNODE *pNextNode = NULL;
ERRNUMNODE **phashnode = NULL;
int i;
int modnum;
if (initialized)
return(0);
hashtable = (ERRNUMNODE**)callocMustSucceed
(NHASH, sizeof(ERRNUMNODE*),"errSymBld");
ERRSYMBOL *errArray = errSymTbl->symbols;
int i;
for (i = 0; i < errSymTbl->nsymbols; i++, errArray++) {
modnum = errArray->errNum >> 16;
if (modnum < 501) {
fprintf(stderr, "errSymBld: ERROR - Module number in errSymTbl < 501 was Module=%lx Name=%s\n",
errArray->errNum, errArray->name);
continue;
}
if ((errSymbolAdd(errArray->errNum, errArray->name)) < 0) {
if (errSymbolAdd(errArray->errNum, errArray->name)) {
fprintf(stderr, "errSymBld: ERROR - errSymbolAdd() failed \n");
continue;
}
}
perrNumNode = (ERRNUMNODE *) ellFirst(&errnumlist);
while (perrNumNode) {
/* hash each perrNumNode->errNum */
epicsUInt16 hashInd = errhash(perrNumNode->errNum);
phashnode = (ERRNUMNODE**)&hashtable[hashInd];
pNextNode = (ERRNUMNODE*) *phashnode;
/* search for last node (NULL) of hashnode linked list */
while (pNextNode) {
phashnode = &pNextNode->hashnode;
pNextNode = *phashnode;
}
*phashnode = perrNumNode;
perrNumNode = (ERRNUMNODE *) ellNext((ELLNODE *) perrNumNode);
}
initialized = 1;
return(0);
return 0;
}
static void _initErrorHashTable(void *_unused)
{
errHashTable.tableMutexId = epicsMutexMustCreate();
}
static void initErrorHashTable()
{
static epicsThreadOnceId initErrSymOnceFlag = EPICS_THREAD_ONCE_INIT;
epicsThreadOnce(&initErrSymOnceFlag, _initErrorHashTable, NULL);
}
/****************************************************************
@@ -109,21 +92,48 @@ static epicsUInt16 errhash(long errNum)
modnum = (unsigned short) (errNum >> 16);
errnum = (unsigned short) (errNum & 0xffff);
return (((modnum - 500) * 20) + errnum) % NHASH;
return ((modnum - (MIN_MODULE_NUM - 1)) * 20 + errnum) % NHASH;
}
/****************************************************************
* ERRSYMBOLADD
* adds symbols to the master errnumlist as compiled from errSymTbl.c
* adds symbols to the global error symbol hash table
***************************************************************/
int errSymbolAdd(long errNum, const char *name)
{
ERRNUMNODE *pNew = (ERRNUMNODE*) callocMustSucceed(1,
sizeof(ERRNUMNODE), "errSymbolAdd");
ERRNUMNODE *pNextNode = NULL;
ERRNUMNODE **phashnode = NULL;
ERRNUMNODE *pNew = NULL;
int modnum = (epicsUInt16) (errNum >> 16);
if (modnum < MIN_MODULE_NUM)
return S_err_invCode;
initErrorHashTable();
epicsUInt16 hashInd = errhash(errNum);
epicsMutexLock(errHashTable.tableMutexId);
phashnode = (ERRNUMNODE**)&errHashTable.table[hashInd];
pNextNode = (ERRNUMNODE*) *phashnode;
/* search for last node (NULL) of hashnode linked list */
while (pNextNode) {
if (pNextNode->errNum == errNum) {
int notIdentical = strcmp(name, pNextNode->message);
epicsMutexUnlock(errHashTable.tableMutexId);
return notIdentical ? S_err_codeExists : 0;
}
phashnode = &pNextNode->hashnode;
pNextNode = *phashnode;
}
pNew = (ERRNUMNODE*) callocMustSucceed(
1, sizeof(ERRNUMNODE), "errSymbolAdd");
pNew->errNum = errNum;
pNew->message = name;
ellAdd(&errnumlist, (ELLNODE*)pNew);
*phashnode = pNew;
epicsMutexUnlock(errHashTable.tableMutexId);
return 0;
}
@@ -155,31 +165,31 @@ const char* errSymLookupInternal(long status)
if (!status)
return "Ok";
if (!initialized)
errSymBld();
initErrorHashTable();
modNum = (unsigned) status;
modNum >>= 16;
modNum &= 0xffff;
if (modNum <= 500) {
const char * pStr = strerror ((int) status);
if (pStr) {
return pStr;
}
if (modNum < MIN_MODULE_NUM) {
return strerror ((int) status);
}
else {
unsigned hashInd = errhash(status);
phashnode = (ERRNUMNODE**)&hashtable[hashInd];
const char *result = NULL;
epicsMutexLock(errHashTable.tableMutexId);
phashnode = (ERRNUMNODE**)&errHashTable.table[hashInd];
pNextNode = *phashnode;
while (pNextNode) {
if (pNextNode->errNum==status){
return pNextNode->message;
result = pNextNode->message;
break;
}
phashnode = &pNextNode->hashnode;
pNextNode = *phashnode;
}
epicsMutexUnlock(errHashTable.tableMutexId);
return result;
}
return NULL;
}
const char* errSymMsg(long status)
@@ -210,12 +220,12 @@ void errSymDump(void)
int i;
int msgcount = 0;
if (!initialized) errSymBld();
initErrorHashTable();
msgcount = 0;
printf("errSymDump: number of hash slots = %d\n", NHASH);
for (i = 0; i < NHASH; i++) {
ERRNUMNODE **phashnode = &hashtable[i];
ERRNUMNODE **phashnode = &errHashTable.table[i];
ERRNUMNODE *pNextNode = *phashnode;
int count = 0;
@@ -246,14 +256,15 @@ void errSymTestPrint(long errNum)
epicsUInt16 modnum;
epicsUInt16 errnum;
if (!initialized) errSymBld();
initErrorHashTable();
message[0] = '\0';
modnum = (epicsUInt16) (errNum >> 16);
errnum = (epicsUInt16) (errNum & 0xffff);
if (modnum < 501) {
if (modnum < MIN_MODULE_NUM) {
fprintf(stderr, "Usage: errSymTestPrint(long errNum) \n");
fprintf(stderr, "errSymTestPrint: module number < 501 \n");
fprintf(stderr,
"errSymTestPrint: module number < %d\n", MIN_MODULE_NUM);
return;
}
errSymLookup(errNum, message, sizeof(message));
@@ -272,10 +283,11 @@ void errSymTest(epicsUInt16 modnum, epicsUInt16 begErrNum,
long errNum;
epicsUInt16 errnum;
if (!initialized) errSymBld();
if (modnum < 501)
if (modnum < MIN_MODULE_NUM)
return;
initErrorHashTable();
/* print range of error messages */
for (errnum = begErrNum; errnum <= endErrNum; errnum++) {
errNum = modnum << 16;

View File

@@ -16,6 +16,9 @@
#include "libComAPI.h"
#include "epicsTypes.h"
#define S_err_invCode (M_err | 1) /* Invalid error symbol code */
#define S_err_codeExists (M_err | 2) /* Error code already exists */
/* ERRSYMBOL - entry in symbol table */
typedef struct {
long errNum; /* errMessage symbol number */

View File

@@ -92,10 +92,11 @@ static struct {
epicsMutexId msgQueueLock;
/* guarded by msgQueueLock */
int atExit;
int sevToLog;
int toConsole;
int ttyConsole;
unsigned int atExit:1;
unsigned int toConsole:1;
unsigned int ttyConsole:1;
unsigned int closeConsole:1;
FILE *console;
/* A loop counter maintained by errlogThread. */
@@ -466,7 +467,7 @@ int eltc(int yesno)
{
errlogInit(0);
epicsMutexMustLock(pvt.msgQueueLock);
pvt.toConsole = yesno;
pvt.toConsole = yesno ? 1 : 0;
epicsMutexUnlock(pvt.msgQueueLock);
errlogFlush();
return 0;
@@ -474,9 +475,30 @@ int eltc(int yesno)
int errlogSetConsole(FILE *stream)
{
int closeConsole = 0;
errlogInit(0);
if (!stream) {
stream = stderr;
}
#ifdef vxWorks
/* The stderr/stdout we get may become invalid as they are thread specific.
* Also, the FDs are actually maps to some "real" FDs.
* Thus, we clone the stream and link it with a dup of the "real" FD.
* The dup'ed FD will be closed when the FILE handle is fclosed. */
if (fileno(stream) <= 2) {
closeConsole = 1;
stream = fdopen(dup(ioTaskStdGet(0, fileno(stream))),"w");
if (!stream) {
return -1;
}
}
#endif
epicsMutexMustLock(pvt.msgQueueLock);
pvt.console = stream ? stream : stderr;
if (pvt.closeConsole) {
fclose(pvt.console);
}
pvt.closeConsole = closeConsole;
pvt.console = stream;
pvt.ttyConsole = isATTY(pvt.console);
epicsMutexUnlock(pvt.msgQueueLock);
/* make sure worker has stopped writing to the previous stream */
@@ -551,7 +573,13 @@ static void errlogInitPvt(void *arg)
pvt.maxMsgSize = pconfig->maxMsgSize;
ellInit(&pvt.listenerList);
pvt.toConsole = TRUE;
#ifdef vxWorks
pvt.console = fdopen(dup(ioTaskStdGet(0, 2)),"w");
pvt.closeConsole = 1;
#else
pvt.console = stderr;
pvt.closeConsole = 0;
#endif
pvt.ttyConsole = isATTY(stderr);
pvt.waitForWork = epicsEventCreate(epicsEventEmpty);
pvt.listenerLock = epicsMutexCreate();

View File

@@ -97,7 +97,7 @@ LIBCOM_API extern int errVerbose;
* that the output is sent to the errlog task. Unless configured not to, the output
* will appear on the console as well.
*/
LIBCOM_API int errlogPrintf(const char *pformat, ...)
LIBCOM_API int errlogPrintf(EPICS_PRINTF_FMT(const char *pformat), ...)
EPICS_PRINTF_STYLE(1,2);
/**
@@ -118,7 +118,8 @@ LIBCOM_API int errlogVprintf(const char *pformat, va_list pvar);
* \return int Consult printf documentation in C standard library
*/
LIBCOM_API int errlogSevPrintf(const errlogSevEnum severity,
const char *pformat, ...) EPICS_PRINTF_STYLE(2,3);
EPICS_PRINTF_FMT(const char *pformat), ...
) EPICS_PRINTF_STYLE(2,3);
/**
* This function is like ::errlogVprintf except that it adds the severity to the beginning
@@ -239,11 +240,14 @@ LIBCOM_API void errlogFlush(void);
* The remaining arguments are just like the arguments to the C printf routine.
* ::errVerbose determines if the filename and line number are shown.
*/
LIBCOM_API void errPrintf(long status, const char *pFileName, int lineno,
const char *pformat, ...) EPICS_PRINTF_STYLE(4,5);
LIBCOM_API void errPrintf(
long status, const char *pFileName, int lineno,
EPICS_PRINTF_FMT(const char *pformat), ...
) EPICS_PRINTF_STYLE(4,5);
LIBCOM_API int errlogPrintfNoConsole(const char *pformat, ...)
EPICS_PRINTF_STYLE(1,2);
LIBCOM_API int errlogPrintfNoConsole(
EPICS_PRINTF_FMT(const char *pformat), ...
) EPICS_PRINTF_STYLE(1,2);
LIBCOM_API int errlogVprintfNoConsole(const char *pformat,va_list pvar);
/**

View File

@@ -309,7 +309,7 @@ struct Tokenize {
}
}
if (inword) {
if (c == quote) {
if (c == quote && !backslash) {
quote = 0;
}
else {
@@ -329,6 +329,10 @@ struct Tokenize {
}
else {
line[icout++] = c;
if (c == '\\' && !backslash) {
backslash = true;
continue;
}
}
}
}
@@ -559,7 +563,17 @@ char** iocsh_attempt_completion(const char* word, int start, int end)
break;
}
}
err = arg-1u >= size_t(def->pFuncDef->nargs);
if(arg-1u >= size_t(def->pFuncDef->nargs)) {
if(def->pFuncDef->arg
&& def->pFuncDef->nargs
&& def->pFuncDef->arg[def->pFuncDef->nargs-1u]->type == iocshArgArgv)
{
// last argument is variable length
arg = def->pFuncDef->nargs;
} else {
err = true;
}
}
}
if(!err) {

View File

@@ -287,6 +287,7 @@ LIBCOM_API int epicsStdCall iocshRun(const char *cmd, const char* macros);
*
* @param err 0 - success (no op), !=0 - error
* @return The err argument value.
* @since 7.0.3.1
*/
LIBCOM_API int iocshSetError(int err);

View File

@@ -113,8 +113,8 @@ int main(void)
pserver->pfdctx = (void *) fdmgr_init();
if (!pserver->pfdctx) {
free(pserver);
fprintf(stderr, "iocLogServer: %s\n", strerror(errno));
free(pserver);
return IOCLS_ERROR;
}
@@ -149,6 +149,7 @@ int main(void)
fprintf (stderr,
"iocLogServer: a server is already installed on port %u?\n",
(unsigned)ioc_log_port);
free(pserver);
return IOCLS_ERROR;
}
@@ -158,6 +159,7 @@ int main(void)
char sockErrBuf[64];
epicsSocketConvertErrnoToString ( sockErrBuf, sizeof ( sockErrBuf ) );
fprintf(stderr, "iocLogServer: listen err %s\n", sockErrBuf);
free(pserver);
return IOCLS_ERROR;
}
@@ -174,6 +176,7 @@ int main(void)
char sockErrBuf[64];
epicsSocketConvertErrnoToString ( sockErrBuf, sizeof ( sockErrBuf ) );
fprintf(stderr, "iocLogServer: ioctl FIONBIO err %s\n", sockErrBuf);
free(pserver);
return IOCLS_ERROR;
}
@@ -190,6 +193,7 @@ int main(void)
"File access problems to `%s' because `%s'\n",
ioc_log_file_name,
strerror(errno));
free(pserver);
return IOCLS_ERROR;
}
@@ -202,6 +206,7 @@ int main(void)
if (status < 0) {
fprintf(stderr,
"iocLogServer: failed to add read callback\n");
free(pserver);
return IOCLS_ERROR;
}

View File

@@ -30,6 +30,7 @@ macDefExpand(const char *str, MAC_HANDLE *macros)
static const char * pairs[] = { "", "environ", NULL, NULL };
long destCapacity = 128;
char *dest = NULL;
char *newdest = NULL;
int n;
if (macros) {
@@ -61,8 +62,11 @@ macDefExpand(const char *str, MAC_HANDLE *macros)
} else {
size_t unused = destCapacity - ++n;
if (unused >= 20)
dest = realloc(dest, n);
if (unused >= 20) {
newdest = realloc(dest, n);
if (newdest)
dest = newdest;
}
}
done:

View File

@@ -43,8 +43,9 @@ extern "C" {
* \param errorMessage A printf-style error message describing the error.
* \param ... Any parameters required for the error message.
*/
LIBCOM_API void cantProceed(const char *errorMessage, ...)
EPICS_PRINTF_STYLE(1,2);
LIBCOM_API void cantProceed(
EPICS_PRINTF_FMT(const char *errorMessage), ...
) EPICS_PRINTF_STYLE(1,2);
/** \name Memory Allocation Functions
* These versions of calloc() and malloc() never fail, they suspend the

View File

@@ -14,16 +14,22 @@
/** \file epicsExport.h
* \brief Exporting IOC objects.
*
* This header is unique, as it defines epicsExportSharedSymbols and thus
* triggers a transition between importing declarations from other libraries,
* to exporting symbols from our own library. The comments in shareLib.h
* provide more information.
* This header defines macros that allow registering IOC shell commands,
* subroutines, device support, etc.
*
* This header should be included with a trailing comment to make it stand
* out from other includes, something like this:
\code
#include <epicsExport.h> // defines epicsExportSharedSymbols
\endcode
* Because this header defines the `epicsExportSharedSymbols` macro, it triggers
* a transition between importing declarations from other libraries, to
* exporting symbols from our own library. For this reason, it must be included
* *last*, after all other EPICS-related includes. The comments in shareLib.h
* provide more information. It is recommended to mark this with a comment, e.g.
* \code
* #include <epicsExport.h> // defines epicsExportSharedSymbols, do not move
* \endcode
*
* \note Do not use this header solely to enable exporting of symbols. If you
* are implementing a library and need to handle the differences in shared
* libraries between Linux and Windows, refer to the documentation within
* [**makeAPIheader.pl**](makeAPIheader.html) or run `makeAPIheader.pl -h`.
*/
#define epicsExportSharedSymbols

View File

@@ -87,8 +87,8 @@ LIBCOM_API int
* \brief Convert a string to a double type
*
* \param str Pointer to a constant character array
* \param to Pointer to the specified type (this will be set during the conversion)
* \param units Pointer to a char * (this will be set with the units string)
* \param to Pointer to the specified type (this will be set only upon successful conversion)
* \param units Pointer to a char * (this will be set with the units string only upon successful conversion)
* \return Status code (0=OK, see macro definitions for possible errors)
*/
LIBCOM_API int

View File

@@ -231,27 +231,39 @@ int epicsStrPrintEscaped(FILE *fp, const char *s, size_t len)
{
int nout = 0;
if (fp == NULL)
return -1;
if (s == NULL || strlen(s) == 0 || len == 0)
return 0; // No work to do
while (len--) {
char c = *s++;
int rc = 0;
switch (c) {
case '\a': nout += fprintf(fp, "\\a"); break;
case '\b': nout += fprintf(fp, "\\b"); break;
case '\f': nout += fprintf(fp, "\\f"); break;
case '\n': nout += fprintf(fp, "\\n"); break;
case '\r': nout += fprintf(fp, "\\r"); break;
case '\t': nout += fprintf(fp, "\\t"); break;
case '\v': nout += fprintf(fp, "\\v"); break;
case '\\': nout += fprintf(fp, "\\\\"); break;
case '\'': nout += fprintf(fp, "\\'"); break;
case '\"': nout += fprintf(fp, "\\\""); break;
case '\a': rc = fprintf(fp, "\\a"); break;
case '\b': rc = fprintf(fp, "\\b"); break;
case '\f': rc = fprintf(fp, "\\f"); break;
case '\n': rc = fprintf(fp, "\\n"); break;
case '\r': rc = fprintf(fp, "\\r"); break;
case '\t': rc = fprintf(fp, "\\t"); break;
case '\v': rc = fprintf(fp, "\\v"); break;
case '\\': rc = fprintf(fp, "\\\\"); break;
case '\'': rc = fprintf(fp, "\\'"); break;
case '\"': rc = fprintf(fp, "\\\""); break;
default:
if (isprint(0xff & (int)c))
nout += fprintf(fp, "%c", c);
rc = fprintf(fp, "%c", c);
else
nout += fprintf(fp, "\\x%02x", (unsigned char)c);
rc = fprintf(fp, "\\x%02x", (unsigned char)c);
break;
}
if (rc < 0) {
return rc;
} else {
nout += rc;
}
}
return nout;
}

View File

@@ -174,7 +174,7 @@ LIBCOM_API void testPlan(int tests);
* \param ... Any parameters required for the format string.
* \return The value of \p pass.
*/
LIBCOM_API int testOk(int pass, const char *fmt, ...)
LIBCOM_API int testOk(int pass, EPICS_PRINTF_FMT(const char *fmt), ...)
EPICS_PRINTF_STYLE(2, 3);
/** \brief Test result using expression as description
* \param cond Expression to be evaluated and displayed.
@@ -192,13 +192,13 @@ LIBCOM_API int testOkV(int pass, const char *fmt, va_list pvar);
* \param fmt A printf-style format string describing the test.
* \param ... Any parameters required for the format string.
*/
LIBCOM_API void testPass(const char *fmt, ...)
LIBCOM_API void testPass(EPICS_PRINTF_FMT(const char *fmt), ...)
EPICS_PRINTF_STYLE(1, 2);
/** \brief Failing test result with printf-style description.
* \param fmt A printf-style format string describing the test.
* \param ... Any parameters required for the format string.
*/
LIBCOM_API void testFail(const char *fmt, ...)
LIBCOM_API void testFail(EPICS_PRINTF_FMT(const char *fmt), ...)
EPICS_PRINTF_STYLE(1, 2);
/** @} */
@@ -223,7 +223,7 @@ LIBCOM_API void testTodoEnd(void);
* \param fmt A printf-style format string giving the reason for stopping.
* \param ... Any parameters required for the format string.
*/
LIBCOM_API void testAbort(const char *fmt, ...)
LIBCOM_API void testAbort(EPICS_PRINTF_FMT(const char *fmt), ...)
EPICS_PRINTF_STYLE(1, 2);
/** @} */
@@ -231,7 +231,7 @@ LIBCOM_API void testAbort(const char *fmt, ...)
* \param fmt A printf-style format string containing diagnostic information.
* \param ... Any parameters required for the format string.
*/
LIBCOM_API int testDiag(const char *fmt, ...)
LIBCOM_API int testDiag(EPICS_PRINTF_FMT(const char *fmt), ...)
EPICS_PRINTF_STYLE(1, 2);
/** \brief Mark the end of testing.
*/

View File

@@ -19,6 +19,12 @@
*
* These are needed to properly create DLLs on MS Windows.
*
* \note This header file is deprecated. A newer mechanism is available that
* automatically handles the differences in shared libraries between Linux and
* Windows while avoiding the pitfalls of the `epicsExportSharedSymbols` macro.
* If you are implementing a library, refer to the documentation within
* [**makeAPIheader.pl**](makeAPIheader.html) or run `makeAPIheader.pl -h`.
*
* ### USAGE
*
* There are two distinct classes of keywords in this file:

View File

@@ -21,10 +21,6 @@
# error compiler/gcc/compilerSpecific.h is only for use with the gnu compiler
#endif
#ifdef __clang__
# error compiler/gcc/compilerSpecific.h is not for use with the clang compiler
#endif
#if __GNUC__ > 2
# define EPICS_ALWAYS_INLINE __inline__ __attribute__((always_inline))
#else

View File

@@ -44,5 +44,11 @@
#endif /* __cplusplus */
/*
* Enable format-string checking if compiler supports it (if msvc is 2015 or newer)
*/
#if _MSC_VER >= 1900
# define EPICS_PRINTF_FMT(a) _Printf_format_string_ a
#endif
#endif /* ifndef compilerSpecific_h */

View File

@@ -61,4 +61,11 @@
#endif
#endif
#ifndef EPICS_PRINTF_FMT
/*
* No format-string checking annotation
*/
# define EPICS_PRINTF_FMT(a) a
#endif
#endif /* ifndef compilerDependencies_h */

View File

@@ -130,7 +130,8 @@ extern "C" {
* output has been truncated if the return value is `size` or more.
*/
LIBCOM_API int epicsStdCall epicsSnprintf(
char *str, size_t size, const char *format, ...) EPICS_PRINTF_STYLE(3,4);
char *str, size_t size, EPICS_PRINTF_FMT(const char *format), ...
) EPICS_PRINTF_STYLE(3,4);
/**
* \brief epicsVsnprintf() is meant to have the same semantics as the C99
* function vsnprintf()

View File

@@ -41,12 +41,17 @@ epicsMessageQueueCreate(unsigned int capacity, unsigned int maximumMessageSize)
{
struct mq_attr the_attr;
epicsMessageQueueId id = (epicsMessageQueueId)calloc(1, sizeof(*id));
if (!id) {
fprintf (stderr, "Can't allocate message queue: %s\n", strerror(errno));
return NULL;
}
sprintf(id->name, "MQ_%0d", epicsAtomicIncrIntT(&idCnt));
the_attr.mq_maxmsg = capacity;
the_attr.mq_msgsize = maximumMessageSize;
id->id = mq_open(id->name, O_RDWR | O_CREAT | O_EXCL, 0644, &the_attr);
if (id->id <0) {
fprintf (stderr, "Can't create message queue: %s\n", strerror (errno));
free(id);
return NULL;
}
return id;

View File

@@ -22,25 +22,10 @@
#define VC_EXTRALEAN
#define STRICT
/*
* Defining this allows the *much* faster critical
* section mutex primitive to be used. Unfortunately,
* using certain of these functions drops support for W95\W98\WME
* unless we specify "delay loading" when we link with the
* DLL so that DLL entry points are not resolved until they
* are used. The code does have run time switches so
* that the more advanced calls are not called unless
* they are available in the windows OS, but this feature
* isn't going to be very useful unless we specify "delay
* loading" when we link with the DLL.
*
* It appears that the only entry point used here that causes
* portability problems with W95\W98\WME is TryEnterCriticalSection.
*/
#ifndef _WIN32_WINNT
# define _WIN32_WINNT 0x0400
#endif
#include <windows.h>
#if _WIN32_WINNT < 0x0501
# error Minimum supported is Windows XP
#endif
#define EPICS_PRIVATE_API

View File

@@ -12,6 +12,11 @@
* Author: Jeff Hill
*/
/* pull in _WIN32_WINNT* definitions, if _WIN32_WINNT
* is not already defined it is set to max supported by SDK
*/
#include <sdkddkver.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
@@ -32,14 +37,58 @@
#include "epicsAtomic.h"
#include "osdThreadPvt.h"
/* Windows Vista and higher supports fibre functions, but the
* prototypes only appear in the windows SDK 8 and above.
* VS2010 supplies sdk 7, but can be upgraded to later SDK
* To accomodate this we suuply prototypes on, for XP
* fall back to Tls*() which will build and run
* correctly for epicsThreads, but means that TLS allocations from
* epicsThreadImplicitCreate() will continue to leak (for non-EPICS threads).
*
* Also, WINE circa 5.0.3 provides the FLS storage functions, but doesn't
* actually run the dtor function.
*
* we check for existance of _WIN32_WINNT_WIN8 which will only be defined
* in SDK 8 and above. If Visa is detected and SDK < 8 we will supply
* the missing prototypes
*/
#if _WIN32_WINNT >= 0x0600 /* VISTA */
# ifdef _WIN32_WINNT_WIN8 /* Existance means using SDK 8 or higher */
# include <fibersapi.h>
# else
# include <winnt.h> /* for PFLS_CALLBACK_FUNCTION */
/* the fibers api exists in vista and above, but no header is provided until SDK 8 */
WINBASEAPI DWORD WINAPI FlsAlloc(PFLS_CALLBACK_FUNCTION);
WINBASEAPI PVOID WINAPI FlsGetValue(DWORD);
WINBASEAPI BOOL WINAPI FlsSetValue(DWORD , PVOID);
WINBASEAPI BOOL WINAPI FlsFree(DWORD);
# endif
#elif _WIN32_WINNT >= 0x0501 /* Windows XP */
typedef void (WINAPI *xPFLS_CALLBACK_FUNCTION) (void*);
static
DWORD xFlsAlloc(xPFLS_CALLBACK_FUNCTION dtor) {
(void)dtor;
return TlsAlloc();
}
# define FlsAlloc xFlsAlloc
# define FlsSetValue TlsSetValue
# define FlsGetValue TlsGetValue
# define USE_TLSALLOC_FALLBACK
#else
# error Minimum supported is Windows XP
#endif
LIBCOM_API void osdThreadHooksRun(epicsThreadId id);
void setThreadName ( DWORD dwThreadID, LPCSTR szThreadName );
static void WINAPI epicsParmCleanupWIN32 ( void * praw );
typedef struct win32ThreadGlobal {
CRITICAL_SECTION mutex;
ELLLIST threadList;
DWORD tlsIndexThreadLibraryEPICS;
DWORD flsIndexThreadLibraryEPICS;
} win32ThreadGlobal;
typedef struct epicsThreadOSD {
@@ -119,7 +168,6 @@ BOOL WINAPI DllMain (
* Don't allow user's explicitly calling FreeLibrary for Com.dll to yank
* the carpet out from under EPICS threads that are still using Com.dll
*/
#if _WIN32_WINNT >= 0x0501
/*
* Only in WXP
* That's a shame because this is probably much faster
@@ -127,22 +175,7 @@ BOOL WINAPI DllMain (
success = GetModuleHandleEx (
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
( LPCTSTR ) DllMain, & dllHandle );
#else
{
char name[256];
DWORD nChar = GetModuleFileName (
hModule, name, sizeof ( name ) );
if ( nChar && nChar < sizeof ( name ) ) {
dllHandle = LoadLibrary ( name );
if ( ! dllHandle ) {
success = FALSE;
}
}
else {
success = FALSE;
}
}
#endif
if ( success ) {
success = TlsSetValue ( dllHandleIndex, dllHandle );
}
@@ -207,8 +240,8 @@ static win32ThreadGlobal * fetchWin32ThreadGlobal ( void )
InitializeCriticalSection ( & pWin32ThreadGlobal->mutex );
ellInit ( & pWin32ThreadGlobal->threadList );
pWin32ThreadGlobal->tlsIndexThreadLibraryEPICS = TlsAlloc();
if ( pWin32ThreadGlobal->tlsIndexThreadLibraryEPICS == 0xFFFFFFFF ) {
pWin32ThreadGlobal->flsIndexThreadLibraryEPICS = FlsAlloc(&epicsParmCleanupWIN32);
if ( pWin32ThreadGlobal->flsIndexThreadLibraryEPICS == FLS_OUT_OF_INDEXES ) {
DeleteCriticalSection ( & pWin32ThreadGlobal->mutex );
free ( pWin32ThreadGlobal );
pWin32ThreadGlobal = 0;
@@ -233,10 +266,14 @@ static void epicsParmCleanupDataWIN32 ( win32ThreadParam * pParm )
}
}
static void epicsParmCleanupWIN32 ( win32ThreadParam * pParm )
static void WINAPI epicsParmCleanupWIN32 ( void * praw )
{
win32ThreadGlobal * pGbl = fetchWin32ThreadGlobal ();
if ( ! pGbl ) {
win32ThreadParam * pParm = praw;
win32ThreadGlobal * pGbl;
if(!praw)
return;
if ( ! (pGbl = fetchWin32ThreadGlobal ()) ) {
fprintf ( stderr, "epicsParmCleanupWIN32: unable to find ctx\n" );
return;
}
@@ -464,7 +501,7 @@ static unsigned WINAPI epicsWin32ThreadEntry ( LPVOID lpParameter )
if ( pGbl ) {
setThreadName ( pParm->id, pParm->pName );
success = TlsSetValue ( pGbl->tlsIndexThreadLibraryEPICS, pParm );
success = FlsSetValue ( pGbl->flsIndexThreadLibraryEPICS, pParm );
if ( success ) {
osdThreadHooksRun ( ( epicsThreadId ) pParm );
/* printf ( "starting thread %d\n", pParm->id ); */
@@ -482,15 +519,15 @@ static unsigned WINAPI epicsWin32ThreadEntry ( LPVOID lpParameter )
epicsExitCallAtThreadExits ();
/*
* CAUTION: !!!! the thread id might continue to be used after this thread exits !!!!
/* On Windows we could omit this and rely on the callback given to FlsAlloc() to free.
* However < vista doesn't implement FLS at all, and WINE (circa 5.0.3) doesn't
* implement fully (dtor never runs). So for EPICS threads, we explicitly
* free() here.
*/
if ( pGbl ) {
TlsSetValue ( pGbl->tlsIndexThreadLibraryEPICS, (void*)0xdeadbeef );
if(pGbl && FlsSetValue ( pGbl->flsIndexThreadLibraryEPICS, NULL )) {
epicsParmCleanupWIN32 ( pParm );
}
epicsParmCleanupWIN32 ( pParm );
return retStat; /* this indirectly closes the thread handle */
}
@@ -550,7 +587,7 @@ static win32ThreadParam * epicsThreadImplicitCreate ( void )
win32ThreadPriority = GetThreadPriority ( pParm->handle );
assert ( win32ThreadPriority != THREAD_PRIORITY_ERROR_RETURN );
pParm->epicsPriority = epicsThreadGetOsiPriorityValue ( win32ThreadPriority );
success = TlsSetValue ( pGbl->tlsIndexThreadLibraryEPICS, pParm );
success = FlsSetValue ( pGbl->flsIndexThreadLibraryEPICS, pParm );
if ( ! success ) {
epicsParmCleanupWIN32 ( pParm );
pParm = 0;
@@ -638,6 +675,8 @@ epicsThreadId epicsThreadCreateOpt (
return NULL;
}
/* after this point, new thread is responsible to free(pParmWIN32) */
return ( epicsThreadId ) pParmWIN32;
}
@@ -685,7 +724,7 @@ static void* getMyWin32ThreadParam ( win32ThreadGlobal * pGbl )
}
pParm = ( win32ThreadParam * )
TlsGetValue ( pGbl->tlsIndexThreadLibraryEPICS );
FlsGetValue ( pGbl->flsIndexThreadLibraryEPICS );
if ( ! pParm ) {
pParm = epicsThreadImplicitCreate ();
}
@@ -1046,6 +1085,11 @@ LIBCOM_API void epicsStdCall epicsThreadShowAll ( unsigned level )
win32ThreadGlobal * pGbl = fetchWin32ThreadGlobal ();
win32ThreadParam * pParm;
#ifdef USE_TLSALLOC_FALLBACK
fprintf(epicsGetStdout(), "Warning: For this target, use of epicsThread* from non-EPICS threads\n"
" May leak memory. Recommend to upgrade to >= Window Vista\n");
#endif
if ( ! pGbl ) {
return;
}

View File

@@ -639,7 +639,7 @@ epicsFindAddr(void *addr, epicsSymbol *sym_p)
if ( nearest.raw && ( (idx = ARR(c,nearest,0,st_name)) < es->strMap->max ) ) {
sym_p->s_nam = strtab + idx;
sym_p->s_val = (char*)(size_t) ARR(c, nearest, 0, st_value) + es->addr;
sym_p->s_val = (char*)(uintptr_t) ARR(c, nearest, 0, st_value) + es->addr;
}
return 0;

View File

@@ -523,11 +523,16 @@ LIBCOM_API unsigned int epicsStdCall epicsThreadGetStackSize (epicsThreadStackSi
LIBCOM_API void epicsStdCall epicsThreadOnce(epicsThreadOnceId *id, void (*func)(void *), void *arg)
{
static struct epicsThreadOSD threadOnceComplete;
#define EPICS_THREAD_ONCE_DONE &threadOnceComplete
static const struct epicsThreadOSD threadOnceComplete;
#define EPICS_THREAD_ONCE_DONE (void*)&threadOnceComplete
int status;
void *prev, *self;
epicsThreadInit();
if(epicsAtomicGetPtrT((void**)id) == EPICS_THREAD_ONCE_DONE) {
return; /* fast path. global init already done. No need to lock */
}
self = epicsThreadGetIdSelf();
status = mutexLock(&onceLock);
if(status) {
fprintf(stderr,"epicsThreadOnce: pthread_mutex_lock returned %s.\n",
@@ -535,21 +540,25 @@ LIBCOM_API void epicsStdCall epicsThreadOnce(epicsThreadOnceId *id, void (*func)
exit(-1);
}
if (*id != EPICS_THREAD_ONCE_DONE) {
if (*id == EPICS_THREAD_ONCE_INIT) { /* first call */
*id = epicsThreadGetIdSelf(); /* mark active */
/* slow path, check again and attempt to begin */
prev = epicsAtomicCmpAndSwapPtrT((void**)id, EPICS_THREAD_ONCE_INIT, self);
if (prev != EPICS_THREAD_ONCE_DONE) {
if (prev == EPICS_THREAD_ONCE_INIT) { /* first call, already marked active */
status = pthread_mutex_unlock(&onceLock);
checkStatusQuit(status,"pthread_mutex_unlock", "epicsThreadOnce");
func(arg);
status = mutexLock(&onceLock);
checkStatusQuit(status,"pthread_mutex_lock", "epicsThreadOnce");
*id = EPICS_THREAD_ONCE_DONE; /* mark done */
} else if (*id == epicsThreadGetIdSelf()) {
epicsAtomicSetPtrT((void**)id, EPICS_THREAD_ONCE_DONE); /* mark done */
} else if (prev == self) {
status = pthread_mutex_unlock(&onceLock);
checkStatusQuit(status,"pthread_mutex_unlock", "epicsThreadOnce");
cantProceed("Recursive epicsThreadOnce() initialization\n");
} else
while (*id != EPICS_THREAD_ONCE_DONE) {
while ((prev = epicsAtomicGetPtrT((void**)id)) != EPICS_THREAD_ONCE_DONE) {
/* Another thread is in the above func(arg) call. */
status = pthread_mutex_unlock(&onceLock);
checkStatusQuit(status,"pthread_mutex_unlock", "epicsThreadOnce");

View File

@@ -29,6 +29,7 @@
#include "osiSock.h"
#include "fdmgr.h"
#include "epicsString.h"
#include "errSymTbl.h"
/* private between errlog.c and this test */
LIBCOM_API
@@ -197,13 +198,61 @@ void testANSIStrip(void)
#undef testEscape
}
static void testErrorMessageMatches(long status, const char *expected)
{
const char *msg = errSymMsg(status);
testOk(strcmp(msg, expected) == 0,
"Error code %ld returns \"%s\", expected message \"%s\"", status,
msg, expected
);
}
static void testGettingExistingErrorSymbol()
{
testErrorMessageMatches(S_err_invCode, "Invalid error symbol code");
}
static void testGettingNonExistingErrorSymbol()
{
long invented_code = (0x7999 << 16) | 0x9998;
testErrorMessageMatches(invented_code, "<Unknown code>");
}
static void testAddingErrorSymbol()
{
long invented_code = (0x7999 << 16) | 0x9999;
errSymbolAdd(invented_code, "Invented Error Message");
testErrorMessageMatches(invented_code, "Invented Error Message");
}
static void testAddingInvalidErrorSymbol()
{
long invented_code = (500 << 16) | 0x1;
testOk(errSymbolAdd(invented_code, "No matter"),
"Adding error symbol with module code < 501 should fail");
}
static void testAddingExistingErrorSymbol()
{
testOk(errSymbolAdd(S_err_invCode, "Duplicate"),
"Adding an error symbol with an existing error code should fail");
}
static void testAddingExistingErrorSymbolWithSameMessage()
{
long invented_code = (0x7999 << 16) | 0x9997;
errSymbolAdd(invented_code, "Invented Error Message");
testOk(!errSymbolAdd(invented_code, "Invented Error Message"),
"Adding identical error symbol shouldn't fail");
}
MAIN(epicsErrlogTest)
{
size_t mlen, i, N;
char msg[256];
clientPvt pvt, pvt2;
testPlan(48);
testPlan(54);
testANSIStrip();
@@ -414,6 +463,13 @@ MAIN(epicsErrlogTest)
testLogPrefix();
osiSockRelease();
testGettingExistingErrorSymbol();
testGettingNonExistingErrorSymbol();
testAddingErrorSymbol();
testAddingInvalidErrorSymbol();
testAddingExistingErrorSymbol();
testAddingExistingErrorSymbolWithSameMessage();
return testDone();
}
/*

View File

@@ -195,6 +195,42 @@ void testStrTok(void)
testTok(NULL, " \t", "bbb ", "bbb", "");
}
static
void testEpicsStrPrintEscaped(void)
{
const char *filename = "testEpicsStrPrintEscaped";
// Avoid printing to stdout by redirecting everything to a file
FILE *testFile = fopen(filename, "a");
FILE *readOnly = fopen(filename, "r");
testDiag("testEpicsStrPrintEscaped()");
// Passing cases
testOk1(epicsStrPrintEscaped(testFile, "1234", 4) == 4);
testOk1(epicsStrPrintEscaped(testFile, "\a\b\f\n\r\t\v\\\'\"", 10) == 20);
// Failing cases
testOk1(epicsStrPrintEscaped(NULL, "1234", 4) == -1);
// On some platforms (specifially certain versions of MinGW-w64), fprintf
// is broken and does not return -1 when the write operation fails. On
// those platforms, epicsStrPrintEscaped cannot detect failure either, so
// testing that it reports failure does not make sense on those platforms.
// For this reason, we only test this behavior of epcisStrPrintEscaped when
// after checking that fprintf behaves correctly.
if (fprintf(readOnly, "test") == -1) {
testOk1(epicsStrPrintEscaped(readOnly, "1234", 4) == -1);
} else {
testSkip(1, "fprintf is broken on this system");
}
testOk1(epicsStrPrintEscaped(testFile, NULL, 4) == 0);
testOk1(epicsStrPrintEscaped(testFile, "", 4) == 0);
testOk1(epicsStrPrintEscaped(testFile, "1234", 0) == 0);
fclose(testFile);
fclose(readOnly);
remove(filename);
}
MAIN(epicsStringTest)
{
const char * const empty = "";
@@ -209,7 +245,7 @@ MAIN(epicsStringTest)
char *s;
int status;
testPlan(439);
testPlan(446);
testChars();
@@ -407,6 +443,7 @@ MAIN(epicsStringTest)
testDistance();
testStrTok();
testEpicsStrPrintEscaped();
return testDone();
}

View File

@@ -216,6 +216,42 @@ static void timeEpicsThreadPrivateGet ()
printf("epicsThreadPrivateGet() takes %f microseconds\n", delay);
}
static void onceAction(void*) {}
static void timeOnce ()
{
#define NITER 100
epicsThreadOnceId once[NITER];
double tSlow = 0.0, tFast = 0.0,
tSlow2= 0.0, tFast2= 0.0;
unsigned i;
for(i=0; i<NITER; i++) {
epicsUInt64 t0, t1, t2;
once[i] = EPICS_THREAD_ONCE_INIT;
t0 = epicsMonotonicGet();
epicsThreadOnce(&once[i], &onceAction, NULL);
t1 = epicsMonotonicGet();
epicsThreadOnce(&once[i], &onceAction, NULL);
t2 = epicsMonotonicGet();
tSlow += t1 - t0;
tSlow2 += (t1 - t0)*(t1 - t0);
tFast += t2 - t1;
tFast2 += (t2 - t1)*(t2 - t1);
}
tSlow /= NITER;
tSlow2 /= NITER;
tFast /= NITER;
tFast2 /= NITER;
printf("epicsThreadOnce(), slow path %.0f +- %.0f ns, fast path %.0f +- %.0f ns\n",
tSlow, sqrt(tSlow2 - tSlow*tSlow),
tFast, sqrt(tFast2 - tFast*tFast));
#undef NITER
}
MAIN(epicsThreadPerform)
{
@@ -224,5 +260,6 @@ MAIN(epicsThreadPerform)
threadSleepTest ();
epicsThreadGetIdSelfPerfTest ();
timeEpicsThreadPrivateGet ();
timeOnce ();
return 0;
}

View File

@@ -21,6 +21,7 @@
#include "epicsEvent.h"
#include "epicsAssert.h"
#include "epicsGuard.h"
#include "epicsAtomic.h"
#include "tsFreeList.h"
#include "epicsUnitTest.h"
#include "testMain.h"
@@ -84,7 +85,7 @@ private:
delayVerify & operator = ( const delayVerify & );
};
static volatile unsigned expireCount;
static int expireCount;
static epicsEvent expireEvent;
delayVerify::delayVerify ( double expectedDelayIn, epicsTimerQueue &queueIn ) :
@@ -128,7 +129,7 @@ inline void delayVerify::start ( const epicsTime &expireTime )
epicsTimerNotify::expireStatus delayVerify::expire ( const epicsTime &currentTime )
{
this->expireStamp = currentTime;
if ( --expireCount == 0u ) {
if ( epics::atomic::decrement ( expireCount ) == 0u ) {
expireEvent.signal ();
}
return noRestart;
@@ -155,7 +156,7 @@ void testAccuracy ()
}
testOk1 ( timerCount == nTimers );
expireCount = nTimers;
epics::atomic::set ( expireCount, nTimers );
for ( i = 0u; i < nTimers; i++ ) {
epicsTime cur = epicsTime::getCurrent ();
pTimers[i]->setBegin ( cur );