Merge ~dirk.zimoch/epics-base:named-events-backward-compatibility into epics-base:3.15

Proposed by Dirk Zimoch
Status: Superseded
Proposed branch: ~dirk.zimoch/epics-base:named-events-backward-compatibility
Merge into: epics-base:3.15
Diff against target: 433 lines (+240/-41)
8 files modified
documentation/RELEASE_NOTES.html (+18/-1)
src/ioc/db/dbScan.c (+50/-39)
src/ioc/db/dbScan.h (+1/-1)
src/ioc/db/test/Makefile (+15/-0)
src/ioc/db/test/devEventSoft.dbd (+1/-0)
src/ioc/db/test/epicsRunDbTests.c (+2/-0)
src/ioc/db/test/scanEventTest.c (+137/-0)
src/ioc/db/test/scanEventTest.db (+16/-0)
Reviewer Review Type Date Requested Status
EPICS Core Developers Pending
Review via email: mp+337644@code.launchpad.net

This proposal has been superseded by a proposal from 2018-02-16.

Description of the change

Make named soft events backward compatible to calculated numeric events (e.g by calc records).
Every event name that represents a number and that rounds down to an integer 1 ... 255 is a numeric events. If it rounds to 0 or is not finite (nan, +/-inf) it is no event.

To post a comment you must log in.
b2d6b67... by Dirk Zimoch on 2018-02-16

removed obsolete code in scanpel

Revision history for this message
Andrew Johnson (anj) wrote :

Hi Dirk,

You can commit to a branch after you have submitted a Merge Request for it and the new commits will automatically appear in the Merge Request (check this one out, it has your b2d6b67 commit in it). Submitting a *new* MR implies there's something major that is different than before, maybe you rebased the branch. Please don't do resubmits for minor additional changes like this, they are unnecessary and send confusing signals to the reviewers.

I will take a look at this when I get a chance.

Thanks,

- Andrew

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

I haven't had a chance to do any run tests, but from reading the code it looks reasonable.

The only (minor) issue I see is that the test code for eventRecord needs to be moved under src/std/rec/test/

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

Oh, I didn‘t know that.
Dirk

> Am 16.02.2018 um 17:17 schrieb Andrew Johnson <email address hidden>:
>
> Hi Dirk,
>
> You can commit to a branch after you have submitted a Merge Request for it and the new commits will automatically appear in the Merge Request (check this one out, it has your b2d6b67 commit in it). Submitting a *new* MR implies there's something major that is different than before, maybe you rebased the branch. Please don't do resubmits for minor additional changes like this, they are unnecessary and send confusing signals to the reviewers.
>
> I will take a look at this when I get a chance.
>
> Thanks,
>
> - Andrew
> --
> https://code.launchpad.net/~dirk.zimoch/epics-base/+git/epics-base/+merge/337644
> You are the owner of ~dirk.zimoch/epics-base:named-events-backward-compatibility.

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

I had put it in db/test because my intent was to test the db event code,
not to test the records. The records are just the tool to test that the
db code works as intended. But if there is a problem having records in
db/test I see no problem moving it.

Dirk

On 16.02.2018 17:38, mdavidsaver wrote:
> I haven't had a chance to do any run tests, but from reading the code it looks reasonable.
>
> The only (minor) issue I see is that the test code for eventRecord needs to be moved under src/std/rec/test/
>
> Diff comments:
>
>> diff --git a/src/ioc/db/test/Makefile b/src/ioc/db/test/Makefile
>> index 215545f..77e485a 100644
>> --- a/src/ioc/db/test/Makefile
>> +++ b/src/ioc/db/test/Makefile
>> @@ -138,6 +138,21 @@ testHarness_SRCS += dbPutGetTest.db
>> TESTFILES += ../dbPutGetTest.db
>> TESTS += testPutGetTest
>>
>> +TARGETS += $(COMMON_DIR)/scanEventTest.dbd
>> +DBDDEPENDS_FILES += scanEventTest.dbd$(DEP)
>> +scanEventTest_DBD += menuGlobal.dbd
>> +scanEventTest_DBD += menuConvert.dbd
>> +scanEventTest_DBD += menuScan.dbd
>> +scanEventTest_DBD += eventRecord.dbd
>> +scanEventTest_DBD += devEventSoft.dbd
>> +scanEventTest_DBD += calcRecord.dbd
>> +scanEventTest_DBD += dfanoutRecord.dbd
>> +TESTPROD_HOST += scanEventTest
>> +scanEventTest_SRCS += scanEventTest.c
>> +scanEventTest_SRCS += scanEventTest_registerRecordDeviceDriver.cpp
>> +scanEventTest_LIBS = dbRecStd
>> +TESTS += scanEventTest
>
> Tests using libdbRecStd and base.dbd need to be under src/std/rec/test/ (cf. compressTest.c). So the test code needs to move.
>
>> +
>> # This runs all the test programs in a known working order:
>> testHarness_SRCS += epicsRunDbTests.c
>>
>
>

Unmerged commits

b2d6b67... by Dirk Zimoch on 2018-02-16

removed obsolete code in scanpel

1e9826d... by Dirk Zimoch on 2018-02-13

add tests for calculated numeric soft events

0691fc5... by Dirk Zimoch on 2018-02-13

fix scanEvent test

8a3080c... by Dirk Zimoch on 2018-02-13

added test for named soft events

d19afc7... by Dirk Zimoch on 2018-02-12

Updated RELEASE_NOTES.html with soft event fix.

adf5375... by Dirk Zimoch on 2018-02-12

fix scanpel glob matching

7d836d9... by Dirk Zimoch on 2018-02-09

some cleanup and scanpel improvement

51e492f... by Dirk Zimoch on 2018-02-09

Fix numeric events: any number from 0 up to less than 256 is theated as an integer event. Event 0 is no event.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/documentation/RELEASE_NOTES.html b/documentation/RELEASE_NOTES.html
2index 33f12c4..d8dd5d5 100644
3--- a/documentation/RELEASE_NOTES.html
4+++ b/documentation/RELEASE_NOTES.html
5@@ -29,11 +29,28 @@ be put in a locally created base/configure/CONFIG_SITE.local file instead
6 of having go modify or replace the original. A new .gitignore pattern
7 tells git to ignore all configure/*.local files.</p>
8
9+<h3>Fix problem with numeric soft events</h3>
10+<p>Changing from numeric to named soft events introduced an incompatibility
11+when a numeric event is converted from a DOUBLE, e.g. from a calc record.
12+To make named events backward compatible to numeric events, the logic has
13+changed as follows:</p>
14+<ul><li>Leading and trailing spaces are strippd from the event name
15+ <li>Empty event names are no events
16+ <li>If the event name can be converted to double:
17+ <ul><li>If the double is not finite (inf or nan) it is no event.
18+ <li>If the double truncated to integer is between 1 and 255
19+ it is a numeric event. Its name is changed to the decimal
20+ integer representation.
21+ <li>If the double truncated to integer is 0 it is no event.
22+ </ul>
23+ <li>All other cases are named events
24+</ul>
25+<p>Also <code>scanpel</code> has been modified to accept a glob pattern for
26+filtering and to show events with no connected records as well.</p>
27
28 <h2 align="center">Changes from the 3.14 branch since 3.15.5</h2>
29
30 <!-- Insert inherited items immediately below here ... -->
31-
32 <h3>Extend maximum Posix epicsEventWaitWithTimeout() delay</h3>
33
34 <p>The Posix implementation of epicsEventWaitWithTimeout() was limiting the
35diff --git a/src/ioc/db/dbScan.c b/src/ioc/db/dbScan.c
36index e0c48d4..cabd736 100644
37--- a/src/ioc/db/dbScan.c
38+++ b/src/ioc/db/dbScan.c
39@@ -27,6 +27,7 @@
40 #include "epicsEvent.h"
41 #include "epicsExit.h"
42 #include "epicsInterrupt.h"
43+#include "epicsMath.h"
44 #include "epicsMutex.h"
45 #include "epicsPrint.h"
46 #include "epicsRingPointer.h"
47@@ -111,8 +112,8 @@ static char *priorityName[NUM_CALLBACK_PRIORITIES] = {
48 typedef struct event_list {
49 CALLBACK callback[NUM_CALLBACK_PRIORITIES];
50 scan_list scan_list[NUM_CALLBACK_PRIORITIES];
51- struct event_list *next;
52- char event_name[MAX_STRING_SIZE];
53+ struct event_list *next;
54+ char eventname[1]; /* actually arbitrary size */
55 } event_list;
56 static event_list * volatile pevent_list[256];
57 static epicsMutexId event_lock;
58@@ -249,11 +250,6 @@ void scanAdd(struct dbCommon *precord)
59 event_list *pel;
60
61 eventname = precord->evnt;
62- if (strlen(eventname) >= MAX_STRING_SIZE) {
63- recGblRecordError(S_db_badField, (void *)precord,
64- "scanAdd: too long EVNT value");
65- return;
66- }
67 prio = precord->prio;
68 if (prio < 0 || prio >= NUM_CALLBACK_PRIORITIES) {
69 recGblRecordError(-1, (void *)precord,
70@@ -317,24 +313,17 @@ void scanDelete(struct dbCommon *precord)
71 recGblRecordError(-1, (void *)precord,
72 "scanDelete detected illegal SCAN value");
73 } else if (scan == menuScanEvent) {
74- char* eventname;
75 int prio;
76 event_list *pel;
77 scan_list *psl = 0;
78
79- eventname = precord->evnt;
80 prio = precord->prio;
81 if (prio < 0 || prio >= NUM_CALLBACK_PRIORITIES) {
82 recGblRecordError(-1, (void *)precord,
83 "scanDelete detected illegal PRIO field");
84 return;
85 }
86- do /* multithreading: make sure pel is consistent */
87- pel = pevent_list[0];
88- while (pel != pevent_list[0]);
89- for (; pel; pel=pel->next) {
90- if (strcmp(pel->event_name, eventname) == 0) break;
91- }
92+ pel = eventNameToHandle(precord->evnt);
93 if (pel && (psl = &pel->scan_list[prio]))
94 deleteFromList(precord, psl);
95 } else if (scan == menuScanI_O_Intr) {
96@@ -422,14 +411,12 @@ int scanpel(const char* eventname) /* print event list */
97 int prio;
98 event_list *pel;
99
100- do /* multithreading: make sure pel is consistent */
101- pel = pevent_list[0];
102- while (pel != pevent_list[0]);
103- for (; pel; pel = pel->next) {
104- if (!eventname || strcmp(pel->event_name, eventname) == 0) {
105+ for (pel = pevent_list[0]; pel; pel = pel->next) {
106+ if (!eventname || epicsStrGlobMatch(pel->eventname, eventname)) {
107+ printf("Event \"%s\"\n", pel->eventname);
108 for (prio = 0; prio < NUM_CALLBACK_PRIORITIES; prio++) {
109 if (ellCount(&pel->scan_list[prio].list) == 0) continue;
110- sprintf(message, "Event \"%s\" Priority %s", pel->event_name, priorityName[prio]);
111+ sprintf(message, " Priority %s", priorityName[prio]);
112 printList(&pel->scan_list[prio], message);
113 }
114 }
115@@ -480,18 +467,53 @@ event_list *eventNameToHandle(const char *eventname)
116 int prio;
117 event_list *pel;
118 static epicsThreadOnceId onceId = EPICS_THREAD_ONCE_INIT;
119-
120- if (!eventname || eventname[0] == 0)
121- return NULL;
122+ double eventnumber;
123+ size_t namelength;
124+
125+ if (!eventname) return NULL;
126+ while (isspace((unsigned char)eventname[0])) eventname++;
127+ if (!eventname[0]) return NULL;
128+ namelength = strlen(eventname);
129+ while (isspace((unsigned char)eventname[namelength-1])) namelength--;
130+
131+ /* Backward compatibility with numeric events:
132+ Treat any string that represents a double with an
133+ integer part between 0 and 255 the same as the integer
134+ because it is most probably a conversion from double
135+ like from a calc record.
136+ */
137+ if (epicsParseDouble(eventname, &eventnumber, NULL) == 0)
138+ {
139+ if (!finite(eventnumber))
140+ return NULL; /* Inf and NaN are no events */
141+ if (eventnumber >= 0 && eventnumber < 256)
142+ {
143+ if (eventnumber < 1)
144+ return NULL; /* 0 is no event */
145+ if ((pel = pevent_list[(int)eventnumber]) != NULL)
146+ return pel;
147+ }
148+ }
149+ else
150+ eventnumber = 0; /* not a numeric event */
151
152 epicsThreadOnce(&onceId, eventOnce, NULL);
153 epicsMutexMustLock(event_lock);
154 for (pel = pevent_list[0]; pel; pel=pel->next) {
155- if (strcmp(pel->event_name, eventname) == 0) break;
156+ if (strncmp(pel->eventname, eventname, namelength) == 0
157+ && pel->eventname[namelength] == 0)
158+ break;
159 }
160 if (pel == NULL) {
161- pel = dbCalloc(1, sizeof(event_list));
162- strcpy(pel->event_name, eventname);
163+ pel = dbCalloc(1, sizeof(event_list) + namelength);
164+ if (eventnumber > 0)
165+ {
166+ /* backward compatibility: make all numeric events look like integers */
167+ sprintf(pel->eventname, "%i", (int)eventnumber);
168+ pevent_list[(int)eventnumber] = pel;
169+ }
170+ else
171+ strncpy(pel->eventname, eventname, namelength);
172 for (prio = 0; prio < NUM_CALLBACK_PRIORITIES; prio++) {
173 callbackSetUser(&pel->scan_list[prio], &pel->callback[prio]);
174 callbackSetPriority(prio, &pel->callback[prio]);
175@@ -501,12 +523,6 @@ event_list *eventNameToHandle(const char *eventname)
176 }
177 pel->next=pevent_list[0];
178 pevent_list[0]=pel;
179- { /* backward compatibility */
180- char* p;
181- long e = strtol(eventname, &p, 0);
182- if (*p == 0 && e > 0 && e <= 255)
183- pevent_list[e] = pel;
184- }
185 }
186 epicsMutexUnlock(event_lock);
187 return pel;
188@@ -527,13 +543,8 @@ void postEvent(event_list *pel)
189 /* backward compatibility */
190 void post_event(int event)
191 {
192- event_list* pel;
193-
194 if (event <= 0 || event > 255) return;
195- do { /* multithreading: make sure pel is consistent */
196- pel = pevent_list[event];
197- } while (pel != pevent_list[event]);
198- postEvent(pel);
199+ postEvent(pevent_list[event]);
200 }
201
202
203 static void ioscanOnce(void *arg)
204diff --git a/src/ioc/db/dbScan.h b/src/ioc/db/dbScan.h
205index 28d2e13..d310271 100644
206--- a/src/ioc/db/dbScan.h
207+++ b/src/ioc/db/dbScan.h
208@@ -50,7 +50,7 @@ epicsShareFunc void scanCleanup(void);
209
210 epicsShareFunc EVENTPVT eventNameToHandle(const char* event);
211 epicsShareFunc void postEvent(EVENTPVT epvt);
212-epicsShareFunc void post_event(int event) EPICS_DEPRECATED;
213+epicsShareFunc void post_event(int event);
214 epicsShareFunc void scanAdd(struct dbCommon *);
215 epicsShareFunc void scanDelete(struct dbCommon *);
216 epicsShareFunc double scanPeriod(int scan);
217diff --git a/src/ioc/db/test/Makefile b/src/ioc/db/test/Makefile
218index 215545f..77e485a 100644
219--- a/src/ioc/db/test/Makefile
220+++ b/src/ioc/db/test/Makefile
221@@ -138,6 +138,21 @@ testHarness_SRCS += dbPutGetTest.db
222 TESTFILES += ../dbPutGetTest.db
223 TESTS += testPutGetTest
224
225+TARGETS += $(COMMON_DIR)/scanEventTest.dbd
226+DBDDEPENDS_FILES += scanEventTest.dbd$(DEP)
227+scanEventTest_DBD += menuGlobal.dbd
228+scanEventTest_DBD += menuConvert.dbd
229+scanEventTest_DBD += menuScan.dbd
230+scanEventTest_DBD += eventRecord.dbd
231+scanEventTest_DBD += devEventSoft.dbd
232+scanEventTest_DBD += calcRecord.dbd
233+scanEventTest_DBD += dfanoutRecord.dbd
234+TESTPROD_HOST += scanEventTest
235+scanEventTest_SRCS += scanEventTest.c
236+scanEventTest_SRCS += scanEventTest_registerRecordDeviceDriver.cpp
237+scanEventTest_LIBS = dbRecStd
238+TESTS += scanEventTest
239+
240 # This runs all the test programs in a known working order:
241 testHarness_SRCS += epicsRunDbTests.c
242
243diff --git a/src/ioc/db/test/devEventSoft.dbd b/src/ioc/db/test/devEventSoft.dbd
244new file mode 100644
245index 0000000..ec3bc2d
246--- /dev/null
247+++ b/src/ioc/db/test/devEventSoft.dbd
248@@ -0,0 +1 @@
249+device(event,CONSTANT,devEventSoft,"Soft Channel")
250diff --git a/src/ioc/db/test/epicsRunDbTests.c b/src/ioc/db/test/epicsRunDbTests.c
251index 05fedeb..ce41815 100644
252--- a/src/ioc/db/test/epicsRunDbTests.c
253+++ b/src/ioc/db/test/epicsRunDbTests.c
254@@ -29,6 +29,7 @@ int testDbChannel(void);
255 int chfPluginTest(void);
256 int arrShorthandTest(void);
257 int recGblCheckDeadbandTest(void);
258+int scanEventTest(void);
259
260 void epicsRunDbTests(void)
261 {
262@@ -47,6 +48,7 @@ void epicsRunDbTests(void)
263 runTest(arrShorthandTest);
264 runTest(recGblCheckDeadbandTest);
265 runTest(chfPluginTest);
266+ runTest(scanEventTest);
267
268 dbmfFreeChunks();
269
270diff --git a/src/ioc/db/test/scanEventTest.c b/src/ioc/db/test/scanEventTest.c
271new file mode 100644
272index 0000000..9835b7b
273--- /dev/null
274+++ b/src/ioc/db/test/scanEventTest.c
275@@ -0,0 +1,137 @@
276+/*************************************************************************\
277+* Copyright (c) 2018 Paul Scherrer Institut
278+* EPICS BASE is distributed subject to a Software License Agreement found
279+* in file LICENSE that is included with this distribution.
280+ \*************************************************************************/
281+
282+/*
283+ * Author: Dirk Zimoch <dirk.zimoch@psi.ch>
284+ */
285+
286+#include <string.h>
287+#include "dbStaticLib.h"
288+#include "dbAccessDefs.h"
289+#include "dbUnitTest.h"
290+#include "testMain.h"
291+#include "osiFileName.h"
292+#include "dbScan.h"
293+
294+void scanEventTest_registerRecordDeviceDriver(struct dbBase *);
295+
296+/* test name to event number:
297+ num = 0 is no event,
298+ num > 0 is numeric event
299+ num < 0 is string event (use same unique number for aliases)
300+*/
301+const struct {char* name; int num;} events[] = {
302+ {"", 0},
303+ {" ", 0},
304+ {"0", 0},
305+ {"0.000000", 0},
306+ {"-0.00000", 0},
307+ {"0.9", 0},
308+ {"nan", 0},
309+ {"NaN", 0},
310+ {"-NAN", 0},
311+ {"-inf", 0},
312+ {"inf", 0},
313+ {"2", 2},
314+ {"2.000000", 2},
315+ {"2.5", 2},
316+ {" 2.5 ", 2},
317+ {"+0x02", 2},
318+ {"3", 3},
319+ {"info 1", -1},
320+ {" info 1 ", -1},
321+ {"-0.9", -2},
322+ {"-2", -3},
323+ {"-2.000000", -4},
324+ {"-2.5", -5},
325+ {" -2.5 ", -5},
326+};
327+
328+MAIN(scanEventTest)
329+{
330+ int i, e;
331+ int aliases[512] ;
332+ int expected_count[512];
333+ #define INDX(i) 256-events[i].num
334+ #define MAXEV 5
335+
336+ testPlan(NELEMENTS(events)*2+(MAXEV+1)*5);
337+
338+ memset(aliases, 0, sizeof(aliases));
339+ memset(expected_count, 0, sizeof(expected_count));
340+
341+ if (dbReadDatabase(&pdbbase, "scanEventTest.dbd",
342+ "." OSI_PATH_LIST_SEPARATOR ".." OSI_PATH_LIST_SEPARATOR
343+ "../O.Common" OSI_PATH_LIST_SEPARATOR "O.Common", NULL))
344+ testAbort("Database description 'scanEventTest.dbd' not found");
345+
346+ scanEventTest_registerRecordDeviceDriver(pdbbase);
347+ for (i = 0; i < NELEMENTS(events); i++) {
348+ char substitutions[256];
349+ sprintf(substitutions, "N=%d,EVENT=%s", i, events[i].name);
350+ if (dbReadDatabase(&pdbbase, "scanEventTest.db",
351+ "." OSI_PATH_LIST_SEPARATOR "..", substitutions))
352+ testAbort("Error reading test database 'scanEventTest.db'");
353+ }
354+ testIocInitOk();
355+ testDiag("Test if eventNameToHandle() strips spaces and handles numeric events");
356+ for (i = 0; i < NELEMENTS(events); i++) {
357+ EVENTPVT pev = eventNameToHandle(events[i].name);
358+ /* test that some names are not events (num=0) */
359+ if (events[i].num == 0)
360+ testOk(pev == NULL, "\"%s\" -> no event", events[i].name);
361+ else
362+ {
363+ expected_count[INDX(i)]++; /* +1 for postEvent */
364+ if (events[i].num > 0)
365+ {
366+ testOk(pev != NULL, "\"%s\" -> numeric event %d", events[i].name, events[i].num);
367+ expected_count[INDX(i)]++; /* +1 for post_event */
368+ }
369+ else
370+ {
371+ /* test that some strings resolve the same event (num!=0) */
372+ if (!aliases[INDX(i)])
373+ {
374+ aliases[INDX(i)] = i;
375+ testOk(pev != NULL, "\"%s\" -> new named event", events[i].name);
376+ }
377+ else
378+ {
379+ testOk(pev == eventNameToHandle(events[aliases[INDX(i)]].name),
380+ "\"%s\" alias for \"%s\"", events[i].name, events[aliases[INDX(i)]].name);
381+ }
382+ }
383+ }
384+ post_event(events[i].num); /* triggers numeric events only */
385+ postEvent(pev);
386+ }
387+
388+ testDiag("Check calculated numeric events (backward compatibility)");
389+ for (e = 0; e <= MAXEV; e++) {
390+ testdbPutFieldOk("eventnum", DBR_LONG, e);
391+ testdbGetFieldEqual("e1", DBR_LONG, e);
392+ testdbGetFieldEqual("e2", DBR_LONG, e);
393+ testdbPutFieldOk("e3", DBR_LONG, e);
394+ testdbPutFieldOk("e3.PROC", DBR_LONG, 1);
395+ if (e != 0)
396+ for (i = 0; i < NELEMENTS(events); i++)
397+ if (events[i].num == e) {
398+ expected_count[INDX(i)]+=3; /* +1 for eventnum->e1, +1 for e2<-eventnum, +1 for e3 */
399+ break;
400+ }
401+ }
402+
403+ testDiag("Check if events have been processed the expected number of times");
404+ for (i = 0; i < NELEMENTS(events); i++) {
405+ char pvname[100];
406+ sprintf(pvname, "c%d", i);
407+ testDiag("Event \"%s\" expected %d times", events[i].name, expected_count[INDX(i)]);
408+ testdbGetFieldEqual(pvname, DBR_LONG, expected_count[INDX(i)]);
409+ }
410+
411+ return testDone();
412+}
413diff --git a/src/ioc/db/test/scanEventTest.db b/src/ioc/db/test/scanEventTest.db
414new file mode 100644
415index 0000000..9118823
416--- /dev/null
417+++ b/src/ioc/db/test/scanEventTest.db
418@@ -0,0 +1,16 @@
419+record(calc, "c$(N)") {
420+ field(SCAN, "Event")
421+ field(EVNT, "$(EVENT)")
422+ field(CALC, "VAL+1")
423+}
424+record(dfanout, "eventnum") {
425+ field(OUTA, "e1 PP")
426+ field(FLNK, "e2")
427+}
428+record(event, "e1") {
429+}
430+record(event, "e2") {
431+ field(INP, "eventnum")
432+}
433+record(event, "e3") {
434+}

Subscribers

People subscribed via source and target branches