It depends on the detail you want. Do you want it shape specific or just an approximation? You could test against the AABB of the geometries and then do a distance check between the centers (position) of the geometries plus the size of the geometry.
If you have 2 circles that are 5 in radius (r). You can then check if they are intersecting by doing a check on each update. The check tests if the distance of the two circles are r+r away from each other. If they are less than r+r, they must be intersecting.
Problem is that you can have a lot of different sized shapes and for that you need to use the AABB of the geometries instead. And that again creates a new problem: the range check is not always accurate because of oddly sized objects. You could partly get around
this using a tolerance.
All techniques used to prevent tunneling can also be used to detect if tunneling exists. Like the sweeping of geometries.
At timestep 0, your geometry at position p0 and a wall is a p7. If you run an update that moves 5*p, you will arrive at p5 once it updates. All is fine so far. Tunneling happens when you run another timestep and your geometry arrives at p10. It will have
passed the wall at p7, and thus you tunneled it. One solution is to sweep the object using substeps inside the timestep. You simply move the geometry p1..p2..p3..p4..p5 - then do another timestep and p6..p7 - it hits the wall and collision is detected. This
process is illustrated in the FAQ:
What is tunneling?
Don't worry, I saved the best for last. The easiest way and probably the best, is to catch when two geometries intersect (using OnCollision) and then check the distance of the intersection points (contacts) to the edge. This is actually calculated inside
the contacts and is called separation (Contact.Separation). You can use this value to see if tunneling happens - granted that the geometries are big enough to detect the actual tunneling happen. See the example above.
Inside the engine, we use the separation value of the contacts to determine the amount of impulse needed to separate the two geometries. When this separation value becomes to big, we have no easy way of recovering and the geometry gets stuck inside the other
geometry. Depending on the timestep used, you can find this limit and use it to determine if it is unable to recover or not. (thus tunneling happened or the geometry got squashed)